From 66d6bdf31f897f460bae7b9d919b3f02b48e7ef1 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Mon, 22 Jun 2015 20:57:26 -0400 Subject: [PATCH] Add initial test framework Requires various python packages nd the cwrap project's scoket_wrapper and nss_wrapper tools, as well as the krb5kdc and the httpd server and related modules (like mod_session). Signed-off-by: Simo Sorce --- tests/httpd.conf | 134 ++++++++++++++++++++++++ tests/index.html | 1 + tests/magtests.py | 297 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/t_spnego.py | 14 +++ 4 files changed, 446 insertions(+) create mode 100644 tests/httpd.conf create mode 100644 tests/index.html create mode 100755 tests/magtests.py create mode 100755 tests/t_spnego.py diff --git a/tests/httpd.conf b/tests/httpd.conf new file mode 100644 index 0000000..517203f --- /dev/null +++ b/tests/httpd.conf @@ -0,0 +1,134 @@ +ServerRoot "${HTTPROOT}" +ServerName "${HTTPNAME}" +Listen ${HTTPADDR}:${HTTPPORT} + +LoadModule access_compat_module modules/mod_access_compat.so +LoadModule actions_module modules/mod_actions.so +LoadModule alias_module modules/mod_alias.so +LoadModule allowmethods_module modules/mod_allowmethods.so +LoadModule auth_basic_module modules/mod_auth_basic.so +#LoadModule auth_digest_module modules/mod_auth_digest.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authn_dbd_module modules/mod_authn_dbd.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_socache_module modules/mod_authn_socache.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule authz_dbd_module modules/mod_authz_dbd.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule cache_module modules/mod_cache.so +LoadModule cache_disk_module modules/mod_cache_disk.so +LoadModule data_module modules/mod_data.so +LoadModule dbd_module modules/mod_dbd.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule dir_module modules/mod_dir.so +LoadModule dumpio_module modules/mod_dumpio.so +LoadModule echo_module modules/mod_echo.so +LoadModule env_module modules/mod_env.so +LoadModule expires_module modules/mod_expires.so +LoadModule ext_filter_module modules/mod_ext_filter.so +LoadModule filter_module modules/mod_filter.so +LoadModule headers_module modules/mod_headers.so +LoadModule include_module modules/mod_include.so +LoadModule info_module modules/mod_info.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule macro_module modules/mod_macro.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule mime_module modules/mod_mime.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule remoteip_module modules/mod_remoteip.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule session_module modules/mod_session.so +LoadModule session_cookie_module modules/mod_session_cookie.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule slotmem_plain_module modules/mod_slotmem_plain.so +LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +LoadModule socache_dbm_module modules/mod_socache_dbm.so +LoadModule socache_memcache_module modules/mod_socache_memcache.so +LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +LoadModule status_module modules/mod_status.so +LoadModule substitute_module modules/mod_substitute.so +LoadModule suexec_module modules/mod_suexec.so +LoadModule unique_id_module modules/mod_unique_id.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule userdir_module modules/mod_userdir.so +LoadModule version_module modules/mod_version.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so + +LoadModule mpm_prefork_module modules/mod_mpm_prefork.so + +LoadModule auth_gssapi_module mod_auth_gssapi.so + + + + AllowOverride none + Require all denied + + +DocumentRoot "${HTTPROOT}/html" + + AllowOverride None + # Allow open access: + Require all granted + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + + + DirectoryIndex index.html + + + + Require all denied + + +PidFile "${HTTPROOT}/logs/httpd.pid" + + +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +CustomLog "logs/access_log" combined + + +ErrorLog "logs/error_log" +LogLevel debug + + + TypesConfig /etc/mime.types + AddType application/x-compress .Z + AddType application/x-gzip .gz .tgz + AddType text/html .shtml + AddOutputFilter INCLUDES .shtml + + +AddDefaultCharset UTF-8 + +IncludeOptional conf.d/*.conf + +CoreDumpDirectory /tmp + + + AuthType GSSAPI + AuthName "Login" + GssapiSSLonly Off + GssapiUseSessions On + Session On + SessionCookieName gssapi_session path=/spnego;httponly + GssapiCredStore ccache:${HTTPROOT}/tmp/httpd_krb5_ccache + GssapiCredStore client_keytab:${HTTPROOT}/http.keytab + GssapiCredStore keytab:${HTTPROOT}/http.keytab + GssapiBasicAuth Off + GssapiAllowedMech krb5 + Require valid-user + + diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..c5ad10e --- /dev/null +++ b/tests/index.html @@ -0,0 +1 @@ +WORKS diff --git a/tests/magtests.py b/tests/magtests.py new file mode 100755 index 0000000..4d4cb49 --- /dev/null +++ b/tests/magtests.py @@ -0,0 +1,297 @@ +#!/usr/bin/python +# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license. + +import argparse +import glob +import os +import random +import shutil +import signal +from string import Template +import subprocess +import sys +import time + + +def parse_args(): + parser = argparse.ArgumentParser(description='Mod Auth GSSAPI Tests Environment') + parser.add_argument('--path', default='%s/scratchdir' % os.getcwd(), + help="Directory in which tests are run") + + return vars(parser.parse_args()) + + +WRAP_HOSTNAME = "kdc.mag.dev" +WRAP_IPADDR = '127.0.0.9' + +def setup_wrappers(base): + + pkgcfg = subprocess.Popen(['pkg-config', '--exists', 'socket_wrapper']) + pkgcfg.wait() + if pkgcfg.returncode != 0: + raise ValueError('Socket Wrappers not available') + + pkgcfg = subprocess.Popen(['pkg-config', '--exists', 'nss_wrapper']) + pkgcfg.wait() + if pkgcfg.returncode != 0: + raise ValueError('Socket Wrappers not available') + + wrapdir = os.path.join(base, 'wrapdir') + if not os.path.exists(wrapdir): + os.makedirs(wrapdir) + + hosts_file = os.path.join(testdir, 'hosts') + with open(hosts_file, 'w+') as f: + f.write('%s %s' % (WRAP_IPADDR, WRAP_HOSTNAME)) + + wenv = {'LD_PRELOAD': 'libsocket_wrapper.so libnss_wrapper.so', + 'SOCKET_WRAPPER_DIR': wrapdir, + 'SOCKET_WRAPPER_DEFAULT_IFACE': '9', + 'NSS_WRAPPER_HOSTNAME': WRAP_HOSTNAME, + 'NSS_WRAPPER_HOSTS': hosts_file} + + return wenv + + +TESTREALM = "MAG.DEV" +KDC_DBNAME = 'db.file' +KDC_STASH = 'stash.file' +KDC_PASSWORD = 'modauthgssapi' +KRB5_CONF_TEMPLATE = ''' +[libdefaults] + default_realm = ${TESTREALM} + dns_lookup_realm = false + dns_lookup_kdc = false + rdns = false + ticket_lifetime = 24h + forwardable = yes + default_ccache_name = FILE://${TESTDIR}/ccaches/krb5_ccache_XXXXXX + +[realms] + ${TESTREALM} = { + kdc =${WRAP_HOSTNAME} + } + +[domain_realm] + .mag.dev = MAG.DEV + mag.dev = MAG.DEV + +[dbmodules] + ${TESTREALM} = { + database_name = ${KDCDIR}/${KDC_DBNAME} + } +''' +KDC_CONF_TEMPLATE = ''' +[kdcdefaults] + kdc_ports = 88 + kdc_tcp_ports = 88 + restrict_anonymous_to_tgt = true + +[realms] + ${TESTREALM} = { + master_key_type = aes256-cts + max_life = 7d + max_renewable_life = 14d + acl_file = ${KDCDIR}/kadm5.acl + dict_file = /usr/share/dict/words + default_principal_flags = +preauth + admin_keytab = ${TESTREALM}/kadm5.keytab + key_stash_file = ${KDCDIR}/${KDC_STASH} + } +[logging] + kdc = FILE:${KDCLOG} +''' + + +def setup_kdc(testdir, wrapenv): + + # setup kerberos environment + testlog = os.path.join(testdir, 'kerb.log') + krb5conf = os.path.join(testdir, 'krb5.conf') + kdcconf = os.path.join(testdir, 'kdc.conf') + kdcdir = os.path.join(testdir, 'kdc') + kdcstash = os.path.join(kdcdir, KDC_STASH) + kdcdb = os.path.join(kdcdir, KDC_DBNAME) + if os.path.exists(kdcdir): + shutil.rmtree(kdcdir) + os.makedirs(kdcdir) + + t = Template(KRB5_CONF_TEMPLATE) + text = t.substitute({'TESTREALM': TESTREALM, + 'TESTDIR': testdir, + 'KDCDIR': kdcdir, + 'KDC_DBNAME': KDC_DBNAME, + 'WRAP_HOSTNAME': WRAP_HOSTNAME}) + with open(krb5conf, 'w+') as f: + f.write(text) + + t = Template(KDC_CONF_TEMPLATE) + text = t.substitute({'TESTREALM': TESTREALM, + 'KDCDIR': kdcdir, + 'KDCLOG': testlog, + 'KDC_STASH': KDC_STASH}) + with open(kdcconf, 'w+') as f: + f.write(text) + + kdcenv = {'PATH': '/sbin:/bin:/usr/sbin:/usr/bin', + 'KRB5_CONFIG': krb5conf, + 'KRB5_KDC_PROFILE': kdcconf} + kdcenv.update(wrapenv) + + with (open(testlog, 'a')) as logfile: + ksetup = subprocess.Popen(["kdb5_util", "create", "-s", + "-r", TESTREALM, "-P", KDC_PASSWORD], + stdout=logfile, stderr=logfile, + env=kdcenv, preexec_fn=os.setsid) + ksetup.wait() + if ksetup.returncode != 0: + raise ValueError('KDC Setup failed') + + with (open(testlog, 'a')) as logfile: + kdcproc = subprocess.Popen(['krb5kdc', '-n'], + stdout=logfile, stderr=logfile, + env=kdcenv, preexec_fn=os.setsid) + + return kdcproc, kdcenv + + +def kadmin_local(cmd, env, logfile): + ksetup = subprocess.Popen(["kadmin.local", "-q", cmd], + stdout=logfile, stderr=logfile, + env=env, preexec_fn=os.setsid) + ksetup.wait() + if ksetup.returncode != 0: + raise ValueError('Kadmin local [%s] failed' % cmd) + + +USR_NAME = "maguser" +USR_PWD = "magpwd" +SVC_KTNAME = "httpd/http.keytab" +KEY_TYPE = "aes256-cts-hmac-sha1-96:normal" + + +def setup_keys(tesdir, env): + + testlog = os.path.join(testdir, 'kerb.log') + + svc_name = "HTTP/%s" % WRAP_HOSTNAME + svc_keytab = os.path.join(testdir, SVC_KTNAME) + cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, svc_name) + with (open(testlog, 'a')) as logfile: + kadmin_local(cmd, env, logfile) + cmd = "ktadd -k %s -e %s %s" % (svc_keytab, KEY_TYPE, svc_name) + with (open(testlog, 'a')) as logfile: + kadmin_local(cmd, env, logfile) + + cmd = "addprinc -pw %s -e %s %s" % (USR_PWD, KEY_TYPE, USR_NAME) + with (open(testlog, 'a')) as logfile: + kadmin_local(cmd, env, logfile) + + keys_env = { "KRB5_KTNAME": svc_keytab } + keys_env.update(env) + + return keys_env + + +def setup_http(testdir, wrapenv): + + httpdir = os.path.join(testdir, 'httpd') + if os.path.exists(httpdir): + shutil.rmtree(httpdir) + os.makedirs(httpdir) + os.mkdir(os.path.join(httpdir, 'conf.d')) + os.mkdir(os.path.join(httpdir, 'html')) + os.mkdir(os.path.join(httpdir, 'logs')) + os.symlink('/etc/httpd/modules', os.path.join(httpdir, 'modules')) + + shutil.copy('src/.libs/mod_auth_gssapi.so', httpdir) + + with open('tests/httpd.conf') as f: + t = Template(f.read()) + text = t.substitute({'HTTPROOT': httpdir, + 'HTTPNAME': WRAP_HOSTNAME, + 'HTTPADDR': WRAP_IPADDR, + 'HTTPPORT': '80'}) + config = os.path.join(httpdir, 'httpd.conf') + with open(config, 'w+') as f: + f.write(text) + + httpenv = {'PATH': '/sbin:/bin:/usr/sbin:/usr/bin', + 'MALLOC_CHECK_': '3', + 'MALLOC_PERTURB_': str(random.randint(0, 32767) % 255 + 1)} + httpenv.update(wrapenv) + + httpproc = subprocess.Popen(['httpd', '-DFOREGROUND', '-f', config], + env=httpenv, preexec_fn=os.setsid) + + return httpproc + + +def kinit_user(testdir, kdcenv): + testlog = os.path.join(testdir, 'kinit.log') + ccache = os.path.join(testdir, 'k5ccache') + testenv = {'KRB5CCNAME': ccache} + testenv.update(kdcenv) + + with (open(testlog, 'a')) as logfile: + kinit = subprocess.Popen(["kinit", USR_NAME], + stdin=subprocess.PIPE, + stdout=logfile, stderr=logfile, + env=testenv, preexec_fn=os.setsid) + kinit.communicate('%s\n' % USR_PWD) + kinit.wait() + if kinit.returncode != 0: + raise ValueError('kinit failed') + + return testenv + + +def test_spnego_auth(testdir, testenv, testlog): + + spnegodir = os.path.join(testdir, 'httpd', 'html', 'spnego') + os.mkdir(spnegodir) + shutil.copy('tests/index.html', spnegodir) + + with (open(testlog, 'a')) as logfile: + spnego = subprocess.Popen(["tests/t_spnego.py"], + stdout=logfile, stderr=logfile, + env=testenv, preexec_fn=os.setsid) + spnego.wait() + if spnego.returncode != 0: + sys.stderr.write('SPNEGO: FAILED\n') + else: + sys.stderr.write('SPNEGO: SUCCESS\n') + + +if __name__ == '__main__': + + args = parse_args() + + testdir = args['path'] + if os.path.exists(testdir): + shutil.rmtree(testdir) + os.makedirs(testdir) + + processes = dict() + + testlog = os.path.join(testdir, 'tests.log') + + try: + wrapenv = setup_wrappers(testdir) + + kdcproc, kdcenv = setup_kdc(testdir, wrapenv) + processes['KDC(%d)' % kdcproc.pid] = kdcproc + + httpproc = setup_http(testdir, wrapenv) + processes['HTTPD(%d)' % httpproc.pid] = httpproc + + keysenv = setup_keys(testdir, kdcenv) + testenv = kinit_user(testdir, kdcenv) + + test_spnego_auth(testdir, testenv, testlog) + + finally: + with (open(testlog, 'a')) as logfile: + for name in processes: + logfile.write("Killing %s\n" % name) + os.killpg(processes[name].pid, signal.SIGTERM) diff --git a/tests/t_spnego.py b/tests/t_spnego.py new file mode 100755 index 0000000..83a9586 --- /dev/null +++ b/tests/t_spnego.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license. + +import os +import requests +from requests_kerberos import HTTPKerberosAuth, OPTIONAL + + +if __name__ == '__main__': + sess = requests.Session() + url = 'http://%s/spnego/' % os.environ['NSS_WRAPPER_HOSTNAME'] + r = sess.get(url, auth=HTTPKerberosAuth()) + if r.status_code != 200: + raise ValueError('Spnego failed') -- 2.1.4