>From 070a79c93c9e516a5a8aa73cf60e9aeca1d701c1 Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Thu, 14 Aug 2014 17:48:02 -0400
Subject: [PATCH] Add a test framework for gss-proxy

This sets up a kdc using socket_wrapper and nss_wrapper from the cwrap
project, and uses a dirty hack to force gssapi to load the current
proxymech interposer library.

It provisions a service and a user key then runs the interpostest binary
in this artifical environment.

Signed-off-by: Simo Sorce <simo@redhat.com>
---
 proxy/Makefile.am       |   1 +
 proxy/tests/runtests.py | 286 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 287 insertions(+)
 create mode 100755 proxy/tests/runtests.py

diff --git a/proxy/Makefile.am b/proxy/Makefile.am
index c94642167198e92f6cefa5578a45dc922a88e10d..9c06cc62e76bd2fba7eaf56d1979bf195e944215 100644
--- a/proxy/Makefile.am
+++ b/proxy/Makefile.am
@@ -264,6 +264,7 @@ clean-local:
 CLEANFILES = *.X */*.X */*/*.X
 
 tests: all $(check_PROGRAMS)
+	./tests/runtests.py
 
 # RPM-related tasks
 
diff --git a/proxy/tests/runtests.py b/proxy/tests/runtests.py
new file mode 100755
index 0000000000000000000000000000000000000000..fff567d31fee50ee7ffe82c4edf52168e33c0fdc
--- /dev/null
+++ b/proxy/tests/runtests.py
@@ -0,0 +1,286 @@
+#!/usr/bin/python
+# copyright (C) 2014 - GSS-Proxy contributors, see COPYING for the license.
+
+import argparse
+import glob
+import os
+import shutil
+import signal
+from string import Template
+import subprocess
+import time
+
+
+def parse_args():
+    parser = argparse.ArgumentParser(description='GSS-Proxy 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.gssproxy.dev"
+
+
+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('127.0.0.9 %s' % 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 = "GSSPROXY.DEV"
+KDC_DBNAME = 'db.file'
+KDC_STASH = 'stash.file'
+KDC_PASSWORD = 'gssproxy'
+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]
+  .gssproxy.dev = GSSPROXY.DEV
+  gssproxy.dev = GSSPROXY.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 = STDERR
+'''
+
+
+def setup_kdc(testdir, wrapenv):
+
+    # setup kerberos environment
+    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,
+                         '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)
+
+    ksetup = subprocess.Popen(["kdb5_util", "create", "-s",
+                               "-r", TESTREALM, "-P", KDC_PASSWORD],
+                              env=kdcenv, preexec_fn=os.setsid)
+    ksetup.wait()
+    if ksetup.returncode != 0:
+        raise ValueError('KDC Setup failed')
+
+    return kdcenv
+
+
+def kadmin_local(cmd, env):
+    ksetup = subprocess.Popen(["kadmin.local", "-q", cmd],
+                              env=env, preexec_fn=os.setsid)
+    ksetup.wait()
+    if ksetup.returncode != 0:
+        raise ValueError('Kadmin local [%s] failed' % cmd)
+
+
+USR_NAME = "user"
+USR_KTNAME = "user.gssproxy.keytab"
+USR_CCACHE = "krb5ccache_usr"
+SVC_KTNAME = "kdc.gssproxy.keytab"
+KEY_TYPE = "aes256-cts-hmac-sha1-96:normal"
+
+
+def setup_keys(tesdir, env):
+
+    svc_name = "host/%s" % WRAP_HOSTNAME
+    svc_keytab = os.path.join(testdir, SVC_KTNAME)
+    cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, svc_name)
+    kadmin_local(cmd, env)
+    cmd = "ktadd -k %s -e %s %s" % (svc_keytab, KEY_TYPE, svc_name)
+    kadmin_local(cmd, env)
+
+    usr_keytab = os.path.join(testdir, USR_KTNAME)
+    cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, USR_NAME)
+    kadmin_local(cmd, env)
+    cmd = "ktadd -k %s -e %s %s" % (usr_keytab, KEY_TYPE, USR_NAME)
+    kadmin_local(cmd, env)
+
+    keys_env = { "KRB5_KTNAME": svc_keytab,
+                 "KRB5CCNAME": os.path.join(tesdir, USR_CCACHE) }
+    keys_env.update(env)
+
+    return keys_env
+
+
+# This is relative to the path where the test binary is being run
+GSSAPI_SYMLINK_DIR = ".test655"
+GSSPROXY_CONF = '''
+gssproxy_v1		2.16.840.1.113730.3.8.15.1	.libs/proxymech.so		<interposer>
+'''
+
+
+def setup_gssapi_env(testdir, wrapenv):
+
+    libgssapi_dir = os.path.join(testdir, 'libgssapi')
+    libgssapi_mechd_dir = os.path.join(GSSAPI_SYMLINK_DIR, 'mech.d')
+
+    if os.path.exists(libgssapi_dir):
+        shutil.rmtree(libgssapi_dir)
+    os.makedirs(libgssapi_dir)
+
+    if os.path.exists(GSSAPI_SYMLINK_DIR):
+        os.unlink(GSSAPI_SYMLINK_DIR)
+    os.symlink(libgssapi_dir, GSSAPI_SYMLINK_DIR)
+    os.makedirs(libgssapi_mechd_dir)
+
+    # find them all and get the longest name in the hopes
+    # we hit /usr/lib64/libgssapi_krb5.so.2.2 in preference
+    libs = glob.glob("/usr/lib*/libgssapi*.so*")
+    lib = None
+    lib_len = 0
+    for l in libs:
+        if len(l) > lib_len:
+            lib_len = len(l)
+            lib = l
+    if not lib:
+        raise KeyError('Gssapi library not found')
+
+    libgssapi_lib = os.path.join(libgssapi_dir, os.path.basename(lib))
+    libgssapi_conf = os.path.join(libgssapi_mechd_dir, 'gssproxy-mech.conf')
+
+    # horrible, horrible hack to load our own configuration later
+    with open(lib, 'rb') as f:
+        data = f.read()
+    with open(libgssapi_lib, 'wb') as f:
+        f.write(data.replace('/etc/gss/mech.d', libgssapi_mechd_dir))
+
+    with open(libgssapi_conf, 'w+') as f:
+        f.write(GSSPROXY_CONF)
+
+    # first swallow in wrapenv vars if any
+    gssapi_env = dict()
+    gssapi_env.update(wrapenv)
+
+    # then augment preload if any
+    ld_pre = ''
+    if 'LD_PRELOAD' in wrapenv:
+        ld_pre = wrapenv['LD_PRELOAD'] + ' '
+    ld_pre = ld_pre + os.path.join(GSSAPI_SYMLINK_DIR,
+                                   os.path.basename(libgssapi_lib))
+    gssapi_env['LD_PRELOAD'] = ld_pre
+
+    return gssapi_env
+
+
+if __name__ == '__main__':
+
+    args = parse_args()
+
+    testdir = args['path']
+    if os.path.exists(testdir):
+        shutil.rmtree(testdir)
+    os.makedirs(testdir)
+
+    processes = []
+
+    try:
+        wrapenv = setup_wrappers(testdir)
+
+        kdcenv = setup_kdc(testdir, wrapenv)
+
+        kdcproc = subprocess.Popen(['krb5kdc', '-n'],
+                               env=kdcenv, preexec_fn=os.setsid)
+        processes.append(kdcproc)
+
+        keysenv = setup_keys(testdir, kdcenv)
+
+        gssapienv = setup_gssapi_env(testdir, keysenv)
+
+        usr_keytab = os.path.join(testdir, USR_KTNAME)
+        ksetup = subprocess.Popen(["kinit", "-kt", usr_keytab, USR_NAME],
+                                  env=keysenv, preexec_fn=os.setsid)
+        ksetup.wait()
+        if ksetup.returncode != 0:
+            raise ValueError('Kinit %s failed' % USR_NAME)
+
+        print gssapienv
+        itest = subprocess.Popen(["./interposetest", "-t",
+                                  "host@%s" % WRAP_HOSTNAME],
+                                 env=gssapienv)
+        itest.wait()
+        if itest.returncode != 0:
+            raise ValueError('Interposetest failed')
+
+
+    finally:
+        for p in processes:
+            os.killpg(p.pid, signal.SIGTERM)
-- 
1.9.3

