diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/tests/Makefile.in | 9 | ||||
| -rw-r--r-- | src/tests/kdbtest.c | 406 | ||||
| -rw-r--r-- | src/tests/t_kdb.py | 280 | ||||
| -rw-r--r-- | src/util/k5test.py | 3 |
4 files changed, 696 insertions, 2 deletions
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index 5bb2b0410..888695947 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -21,6 +21,10 @@ KTEST_OPTS= $(KADMIN_OPTS) -p $(TEST_PREFIX) -n $(TEST_NUM) -D $(TEST_DEPTH) hist: hist.o $(KDB5_DEPLIBS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o $@ hist.o $(KDB5_LIBS) $(KADMSRV_LIBS) $(KRB5_BASE_LIBS) +kdbtest: kdbtest.o $(KDB5_DEPLIBS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ kdbtest.o $(KDB5_LIBS) $(KADMSRV_LIBS) \ + $(KRB5_BASE_LIBS) + check-unix:: kdb_check kdc.conf: Makefile @@ -63,7 +67,7 @@ kdb_check: kdc.conf krb5.conf $(RUN_SETUP) $(VALGRIND) ../kadmin/dbutil/kdb5_util $(KADMIN_OPTS) destroy -f $(RM) $(TEST_DB)* stash_file -check-pytests:: hist +check-pytests:: hist kdbtest $(RUNPYTEST) $(srcdir)/t_general.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_iprop.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_anonpkinit.py $(PYTESTFLAGS) @@ -82,10 +86,11 @@ check-pytests:: hist $(RUNPYTEST) $(srcdir)/t_keytab.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_pwhist.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_kadmin_acl.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_kdb.py $(PYTESTFLAGS) # $(RUNPYTEST) $(srcdir)/kdc_realm/kdcref.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cve-2012-1014.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cve-2012-1015.py $(PYTESTFLAGS) clean:: $(RM) krb5.conf kdc.conf - $(RM) -rf kdc_realm/sandbox + $(RM) -rf kdc_realm/sandbox ldap diff --git a/src/tests/kdbtest.c b/src/tests/kdbtest.c new file mode 100644 index 000000000..11c433de0 --- /dev/null +++ b/src/tests/kdbtest.c @@ -0,0 +1,406 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* tests/kdbtest.c - test program to exercise KDB modules */ +/* + * Copyright (C) 2012 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This test program uses libkdb5 APIs to exercise as much of the LDAP and DB2 + * back ends. + */ + +#include <krb5.h> +#include <kadm5/admin.h> +#include <string.h> + +static krb5_context ctx; + +#define CHECK(code) check(code, __LINE__) +#define CHECK_COND(val) check_cond(val, __LINE__) + +static void +check(krb5_error_code code, int lineno) +{ + const char *errmsg; + + if (code) { + errmsg = krb5_get_error_message(ctx, code); + fprintf(stderr, "Unexpected error at line %d: %s\n", lineno, errmsg); + krb5_free_error_message(ctx, errmsg); + exit(1); + } +} + +static void +check_cond(int value, int lineno) +{ + if (!value) { + fprintf(stderr, "Unexpected result at line %d\n", lineno); + exit(1); + } +} + +static krb5_data princ_data[2] = { + { KV5M_DATA, 3, "xyz" }, + { KV5M_DATA, 3, "abc" } +}; + +static krb5_principal_data sample_princ = { + KV5M_PRINCIPAL, + { KV5M_DATA, 11, "KRBTEST.COM" }, + princ_data, 2, KRB5_NT_UNKNOWN +}; + +static krb5_principal_data xrealm_princ = { + KV5M_PRINCIPAL, + { KV5M_DATA, 12, "KRBTEST2.COM" }, + princ_data, 2, KRB5_NT_UNKNOWN +}; + +#define U(x) (unsigned char *)x + +/* + * tl1 through tl4 are normalized to attributes in the LDAP back end. tl5 is + * stored as untranslated tl-data. tl3 contains an encoded osa_princ_ent with + * a policy reference to "testpol". + */ +static krb5_tl_data tl5 = { NULL, KRB5_TL_MKVNO, 2, U("\0\1") }; +static krb5_tl_data tl4 = { &tl5, KRB5_TL_LAST_ADMIN_UNLOCK, 4, + U("\6\0\0\0") }; +static krb5_tl_data tl3 = { &tl4, KRB5_TL_KADM_DATA, 32, + U("\x12\x34\x5C\x01\x00\x00\x00\x08" + "\x74\x65\x73\x74\x70\x6F\x6C\x00" + "\x00\x00\x08\x00\x00\x00\x00\x00" + "\x00\x00\x00\x02\x00\x00\x00\x00") }; +static krb5_tl_data tl2 = { &tl3, KRB5_TL_MOD_PRINC, 8, U("\5\6\7\0x@Y\0") }; +static krb5_tl_data tl1 = { &tl2, KRB5_TL_LAST_PWD_CHANGE, 4, U("\1\2\3\4") }; + +/* An encoded osa_print_enc with no policy reference. */ +static krb5_tl_data tl_no_policy = { NULL, KRB5_TL_KADM_DATA, 24, + U("\x12\x34\x5C\x01\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x02\x00\x00\x00\x00") }; + +static krb5_key_data keys[] = { + { + 2, /* key_data_ver */ + 2, /* key_data_kvno */ + { ENCTYPE_AES256_CTS_HMAC_SHA1_96, KRB5_KDB_SALTTYPE_SPECIAL }, + { 32, 7 }, + { U("\x17\xF2\x75\xF2\x95\x4F\x2E\xD1" + "\xF9\x0C\x37\x7B\xA7\xF4\xD6\xA3" + "\x69\xAA\x01\x36\xE0\xBF\x0C\x92" + "\x7A\xD6\x13\x3C\x69\x37\x59\xA9"), + U("expsalt") } + }, + { + 1, /* key_data_ver */ + 2, /* key_data_kvno */ + { ENCTYPE_AES128_CTS_HMAC_SHA1_96, 0 }, + { 16, 0 }, + { U("\xDC\xEE\xB7\x0B\x3D\xE7\x65\x62" + "\xE6\x89\x22\x6C\x76\x42\x91\x48"), + NULL } + } +}; +#undef U + +static krb5_db_entry sample_entry = { + 0, + KRB5_KDB_V1_BASE_LENGTH, + /* mask */ + KADM5_PRINCIPAL | KADM5_PRINC_EXPIRE_TIME | KADM5_PW_EXPIRATION | + KADM5_ATTRIBUTES | KADM5_MAX_LIFE | KADM5_POLICY | KADM5_MAX_RLIFE | + KADM5_LAST_SUCCESS | KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT | + KADM5_KEY_DATA | KADM5_TL_DATA, + /* attributes */ + KRB5_KDB_REQUIRES_PRE_AUTH | KRB5_KDB_REQUIRES_HW_AUTH | + KRB5_KDB_DISALLOW_SVR, + 1234, /* max_life */ + 5678, /* max_renewable_life */ + 9012, /* expiration */ + 3456, /* pw_expiration */ + 1, /* last_success */ + 5, /* last_failed */ + 2, /* fail_auth_count */ + 5, /* n_tl_data */ + 2, /* n_key_data */ + 0, NULL, /* e_length, e_data */ + &sample_princ, + &tl1, + keys +}; + +static osa_policy_ent_rec sample_policy = { + 0, /* version */ + "testpol", /* name */ + 1357, /* pw_min_life */ + 100, /* pw_max_life */ + 6, /* pw_min_length */ + 2, /* pw_min_classes */ + 3, /* pw_history_num */ + 1, /* policy_refcnt */ + 2, /* pw_max_fail */ + 60, /* pw_failcnt_interval */ + 120, /* pw_lockout_duration */ + 0, /* attributes */ + 2468, /* max_life */ + 3579, /* max_renewable_life */ + "aes", /* allowed_keysalts */ + 0, NULL /* n_tl_data, tl_data */ +}; + +/* Compare pol against sample_policy. */ +static void +check_policy(osa_policy_ent_t pol) +{ + CHECK_COND(strcmp(pol->name, sample_policy.name) == 0); + CHECK_COND(pol->pw_min_life == sample_policy.pw_min_life); + CHECK_COND(pol->pw_max_life == sample_policy.pw_max_life); + CHECK_COND(pol->pw_min_length == sample_policy.pw_min_length); + CHECK_COND(pol->pw_min_classes == sample_policy.pw_min_classes); + CHECK_COND(pol->pw_history_num == sample_policy.pw_history_num); + CHECK_COND(pol->pw_max_life == sample_policy.pw_max_life); + CHECK_COND(pol->pw_failcnt_interval == sample_policy.pw_failcnt_interval); + CHECK_COND(pol->pw_lockout_duration == sample_policy.pw_lockout_duration); + CHECK_COND(pol->attributes == sample_policy.attributes); + CHECK_COND(pol->max_life == sample_policy.max_life); + CHECK_COND(pol->max_renewable_life == sample_policy.max_renewable_life); + CHECK_COND(strcmp(pol->allowed_keysalts, + sample_policy.allowed_keysalts) == 0); +} + +/* Compare ent against sample_entry. */ +static void +check_entry(krb5_db_entry *ent) +{ + krb5_int16 i, j; + krb5_key_data *k1, *k2; + krb5_tl_data *tl, etl; + + CHECK_COND(ent->attributes == sample_entry.attributes); + CHECK_COND(ent->max_life == sample_entry.max_life); + CHECK_COND(ent->max_renewable_life == sample_entry.max_renewable_life); + CHECK_COND(ent->expiration == sample_entry.expiration); + CHECK_COND(ent->pw_expiration == sample_entry.pw_expiration); + CHECK_COND(ent->last_success == sample_entry.last_success); + CHECK_COND(ent->last_failed == sample_entry.last_failed); + CHECK_COND(ent->fail_auth_count == sample_entry.fail_auth_count); + CHECK_COND(krb5_principal_compare(ctx, ent->princ, sample_entry.princ)); + CHECK_COND(ent->n_key_data == sample_entry.n_key_data); + for (i = 0; i < ent->n_key_data; i++) { + k1 = &ent->key_data[i]; + k2 = &sample_entry.key_data[i]; + CHECK_COND(k1->key_data_ver == k2->key_data_ver); + CHECK_COND(k1->key_data_kvno == k2->key_data_kvno); + for (j = 0; j < k1->key_data_ver; j++) { + CHECK_COND(k1->key_data_type[j] == k2->key_data_type[j]); + CHECK_COND(k1->key_data_length[j] == k2->key_data_length[j]); + CHECK_COND(memcmp(k1->key_data_contents[j], + k2->key_data_contents[j], + k1->key_data_length[j]) == 0); + } + } + for (tl = sample_entry.tl_data; tl != NULL; tl = tl->tl_data_next) { + etl.tl_data_type = tl->tl_data_type; + CHECK(krb5_dbe_lookup_tl_data(ctx, ent, &etl)); + CHECK_COND(tl->tl_data_length == etl.tl_data_length); + CHECK_COND(memcmp(tl->tl_data_contents, etl.tl_data_contents, + tl->tl_data_length) == 0); + } +} + +/* Audit a successful or failed preauth attempt for *entp. Then reload *entp + * (by fetching sample_princ) so we can see the effect. */ +static void +sim_preauth(krb5_timestamp authtime, krb5_boolean ok, krb5_db_entry **entp) +{ + /* Both back ends ignore the request parameter for now. */ + krb5_db_audit_as_req(ctx, NULL, *entp, *entp, authtime, + ok ? 0 : KRB5KDC_ERR_PREAUTH_FAILED); + krb5_db_free_principal(ctx, *entp); + CHECK(krb5_db_get_principal(ctx, &sample_princ, 0, entp)); +} + +static krb5_error_code +iter_princ_handler(void *data, krb5_db_entry *ent) +{ + int *count = data; + + CHECK_COND(krb5_principal_compare(ctx, ent->princ, sample_entry.princ)); + (*count)++; + return 0; +} + +static void +iter_pol_handler(void *data, osa_policy_ent_t pol) +{ + int *count = data; + + CHECK_COND(strcmp(pol->name, sample_policy.name) == 0); + (*count)++; +} + +int +main() +{ + krb5_db_entry *ent; + osa_policy_ent_t pol; + krb5_pa_data **e_data; + const char *status; + char *defrealm; + int count; + + CHECK(krb5_init_context_profile(NULL, KRB5_INIT_CONTEXT_KDC, &ctx)); + + /* Currently necessary for krb5_db_open to work. */ + CHECK(krb5_get_default_realm(ctx, &defrealm)); + + /* If we can, revert to requiring all entries match sample_princ in + * iter_princ_handler */ + CHECK_COND(krb5_db_inited(ctx) != 0); + CHECK(krb5_db_create(ctx, NULL)); + CHECK(krb5_db_inited(ctx)); + CHECK(krb5_db_fini(ctx)); + CHECK_COND(krb5_db_inited(ctx) != 0); + + CHECK_COND(krb5_db_inited(ctx) != 0); + CHECK(krb5_db_open(ctx, NULL, KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN)); + CHECK(krb5_db_inited(ctx)); + + /* Manipulate a policy, leaving testpol in place at the end. */ + CHECK_COND(krb5_db_put_policy(ctx, &sample_policy) != 0); + CHECK_COND(krb5_db_delete_policy(ctx, "testpol") != 0); + CHECK_COND(krb5_db_get_policy(ctx, "testpol", &pol) == KRB5_KDB_NOENTRY); + CHECK(krb5_db_create_policy(ctx, &sample_policy)); + CHECK_COND(krb5_db_create_policy(ctx, &sample_policy) != 0); + CHECK(krb5_db_get_policy(ctx, "testpol", &pol)); + check_policy(pol); + pol->pw_min_length--; + CHECK(krb5_db_put_policy(ctx, pol)); + krb5_db_free_policy(ctx, pol); + CHECK(krb5_db_get_policy(ctx, "testpol", &pol)); + CHECK_COND(pol->pw_min_length == sample_policy.pw_min_length - 1); + krb5_db_free_policy(ctx, pol); + CHECK(krb5_db_delete_policy(ctx, "testpol")); + CHECK_COND(krb5_db_put_policy(ctx, &sample_policy) != 0); + CHECK_COND(krb5_db_delete_policy(ctx, "testpol") != 0); + CHECK_COND(krb5_db_get_policy(ctx, "testpol", &pol) == KRB5_KDB_NOENTRY); + CHECK(krb5_db_create_policy(ctx, &sample_policy)); + count = 0; + CHECK(krb5_db_iter_policy(ctx, NULL, iter_pol_handler, &count)); + CHECK_COND(count == 1); + + /* Create a principal. */ + CHECK_COND(krb5_db_delete_principal(ctx, &sample_princ) == + KRB5_KDB_NOENTRY); + CHECK_COND(krb5_db_get_principal(ctx, &xrealm_princ, 0, &ent) == + KRB5_KDB_NOENTRY); + CHECK(krb5_db_put_principal(ctx, &sample_entry)); + /* Putting again will fail with LDAP (due to KADM5_PRINCIPAL in mask) + * but succeed with DB2, so don't check the result. */ + (void)krb5_db_put_principal(ctx, &sample_entry); + /* But it should succeed in both back ends with KADM5_LOAD in mask. */ + sample_entry.mask |= KADM5_LOAD; + CHECK(krb5_db_put_principal(ctx, &sample_entry)); + sample_entry.mask &= ~KADM5_LOAD; + /* Fetch and compare the added principal. */ + CHECK(krb5_db_get_principal(ctx, &sample_princ, 0, &ent)); + check_entry(ent); + + /* We can't set up a successful allowed-to-delegate check through existing + * APIs yet, but we can make a failed check. */ + CHECK_COND(krb5_db_check_allowed_to_delegate(ctx, &sample_princ, ent, + &sample_princ) != 0); + + /* Exercise lockout code. */ + /* Policy params: max_fail 2, failcnt_interval 60, lockout_duration 120 */ + /* Initial state: last_success 1, last_failed 5, fail_auth_count 2, + * last admin unlock 6 */ + /* Check succeeds due to last admin unlock. */ + CHECK(krb5_db_check_policy_as(ctx, NULL, ent, ent, 7, &status, &e_data)); + /* Failure count resets to 1 due to last admin unlock. */ + sim_preauth(8, FALSE, &ent); + CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 8); + /* Failure count resets to 1 due to failcnt_interval */ + sim_preauth(70, FALSE, &ent); + CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 70); + /* Failure count resets to 0 due to successful preauth. */ + sim_preauth(75, TRUE, &ent); + CHECK_COND(ent->fail_auth_count == 0 && ent->last_success == 75); + /* Failure count increments to 2 and stops incrementing. */ + sim_preauth(80, FALSE, &ent); + CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 80); + sim_preauth(100, FALSE, &ent); + CHECK_COND(ent->fail_auth_count == 2 && ent->last_failed == 100); + sim_preauth(110, FALSE, &ent); + CHECK_COND(ent->fail_auth_count == 2 && ent->last_failed == 100); + /* Check fails due to reaching maximum failure count. */ + CHECK_COND(krb5_db_check_policy_as(ctx, NULL, ent, ent, 170, &status, + &e_data) == KRB5KDC_ERR_CLIENT_REVOKED); + /* Check succeeds after lockout_duration has passed. */ + CHECK(krb5_db_check_policy_as(ctx, NULL, ent, ent, 230, &status, &e_data)); + /* Failure count resets to 1 on next failure. */ + sim_preauth(240, FALSE, &ent); + CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 240); + + /* Exercise LDAP code to clear a policy reference and to set the key + * data on an existing principal. */ + CHECK(krb5_dbe_update_tl_data(ctx, ent, &tl_no_policy)); + ent->mask = KADM5_POLICY_CLR | KADM5_KEY_DATA; + CHECK(krb5_db_put_principal(ctx, ent)); + /* Deleting testpol should work now that the reference is gone. */ + CHECK(krb5_db_delete_policy(ctx, "testpol")); + + /* Put the modified entry again (with KDB_TL_USER_INFO tl-data for LDAP) as + * from a load operation. */ + ent->mask = (sample_entry.mask & ~KADM5_POLICY) | KADM5_LOAD; + CHECK(krb5_db_put_principal(ctx, ent)); + + /* Exercise LDAP code to create a new principal at a DN from + * KDB_TL_USER_INFO tl-data. */ + CHECK(krb5_db_delete_principal(ctx, &sample_princ)); + CHECK(krb5_db_put_principal(ctx, ent)); + krb5_db_free_principal(ctx, ent); + + /* Exercise principal iteration code. */ + count = 0; + CHECK(krb5_db_iterate(ctx, "xyz*", iter_princ_handler, &count)); + CHECK_COND(count == 1); + + CHECK(krb5_db_fini(ctx)); + CHECK_COND(krb5_db_inited(ctx) != 0); + + /* It might be nice to exercise krb5_db_destroy here, but the LDAP module + * doesn't support it. */ + + krb5_free_default_realm(ctx, defrealm); + krb5_free_context(ctx); + return 0; +} diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py new file mode 100644 index 000000000..dd79b4283 --- /dev/null +++ b/src/tests/t_kdb.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +from k5test import * +import time + +# Return the location of progname in tht executable path, or None if +# it is not found. +def which(progname): + for dir in os.environ["PATH"].split(os.pathsep): + path = os.path.join(dir, progname) + if os.access(path, os.X_OK): + return path + return None + +# Run kdbtest against the BDB module. +realm = K5Realm(create_kdb=False) +realm.run_as_master(['./kdbtest']) + +# Set up an OpenLDAP test server if we can. + +if (not os.path.exists(os.path.join(plugins, 'kdb', 'kldap.so')) and + not os.path.exists(os.path.join(buildtop, 'lib', 'libkdb_ldap.a'))): + success('Warning: not testing LDAP back end because it is not built') + exit(0) + +system_slapd = which('slapd') +if not system_slapd: + success('Warning: not testing LDAP module because slapd not found') + exit(0) + +ldapdir = os.path.abspath('ldap') +slapd = os.path.join(ldapdir, 'slapd') +dbdir = os.path.join(ldapdir, 'ldap') +slapd_conf = os.path.join(ldapdir, 'slapd.conf') +slapd_out = os.path.join(ldapdir, 'slapd.out') +slapd_pidfile = os.path.join(ldapdir, 'pid') +ldap_pwfile = os.path.join(ldapdir, 'pw') +ldap_sock = os.path.join(ldapdir, 'sock') +ldap_uri = 'ldapi://%s/' % ldap_sock.replace(os.path.sep, '%2F') +schema = os.path.join(srctop, 'plugins', 'kdb', 'ldap', 'libkdb_ldap', + 'kerberos.schema') +top_dn = 'cn=krb5' +admin_dn = 'cn=admin,cn=krb5' +admin_pw = 'admin' + +shutil.rmtree(ldapdir, True) +os.mkdir(ldapdir) +os.mkdir(dbdir) + +# Some Linux installations have AppArmor or similar restrictions on +# the slapd binary, which would prevent it from accessing the build +# directory. Try to defeat this by copying the binary. +shutil.copy(system_slapd, slapd) + +# Make a slapd config file. This is deprecated in OpenLDAP 2.3 and +# later, but it's easier than using LDIF and slapadd. +file = open(slapd_conf, 'w') +file.write('pidfile %s\n' % slapd_pidfile) +file.write('include %s\n' % schema) +file.write('moduleload back_bdb\n') +file.write('database bdb\n') +file.write('suffix %s\n' % top_dn) +file.write('rootdn %s\n' % admin_dn) +file.write('rootpw %s\n' % admin_pw) +file.write('directory %s\n' % dbdir) +file.close() + +slapd_pid = -1 +def kill_slapd(): + global slapd_pid + if slapd_pid != -1: + os.kill(slapd_pid, signal.SIGTERM) + slapd_pid = -1 +atexit.register(kill_slapd) + +out = open(slapd_out, 'w') +subprocess.call([slapd, '-h', ldap_uri, '-f', slapd_conf], stdout=out, + stderr=out) +out.close() +pidf = open(slapd_pidfile, 'r') +slapd_pid = int(pidf.read()) +pidf.close() +output('*** Started slapd (pid %d, output in %s)\n' % (slapd_pid, slapd_out)) + +# slapd detaches before it finishes setting up its listener sockets +# (they are bound but listen() has not been called). Give it a second +# to finish. +time.sleep(1) + +# Run kdbtest against the LDAP module. +kdc_conf = {'all': { + 'realms': {'$realm': {'database_module': 'ldap'}}, + 'dbmodules': {'ldap': { + 'db_library': 'kldap', + 'ldap_kerberos_container_dn': top_dn, + 'ldap_kdc_dn': admin_dn, + 'ldap_kadmind_dn': admin_dn, + 'ldap_service_password_file': ldap_pwfile, + 'ldap_servers': ldap_uri}}}} +realm = K5Realm(create_kdb=False, kdc_conf=kdc_conf) +input = admin_pw + '\n' + admin_pw + '\n' +realm.run_as_master([kdb5_ldap_util, 'stashsrvpw', admin_dn], input=input) +realm.run_as_master(['./kdbtest']) + +# Run a kdb5_ldap_util command using the test server's admin DN and password. +def kldaputil(args, **kw): + return realm.run_as_master([kdb5_ldap_util, '-D', admin_dn, '-w', + admin_pw] + args, **kw) + +# kdbtest can't currently clean up after itself since the LDAP module +# doesn't support krb5_db_destroy. So clean up after it with +# kdb5_ldap_util before proceeding. +kldaputil(['destroy', '-f']) + +ldapadd = which('ldapadd') +if not ldapadd: + success('Warning: skipping some LDAP tests because ldapadd not found') + exit(0) + +def ldap_add(dn, objectclass, attrs=[]): + proc = subprocess.Popen([ldapadd, '-H', ldap_uri, '-D', admin_dn, '-x', + '-w', admin_pw], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + in_data = 'dn: %s\nobjectclass: %s\n' % (dn, objectclass) + in_data += '\n'.join(attrs) + '\n' + (out, dummy) = proc.communicate(in_data) + output(out) + +# Create krbContainer objects for use as subtrees. +ldap_add('cn=t1,cn=krb5', 'krbContainer') +ldap_add('cn=t2,cn=krb5', 'krbContainer') +ldap_add('cn=x,cn=t1,cn=krb5', 'krbContainer') +ldap_add('cn=y,cn=t2,cn=krb5', 'krbContainer') + +# Create a realm, exercising all of the realm options. +kldaputil(['create', '-s', '-P', 'master', '-subtrees', 'cn=t2,cn=krb5', + '-containerref', 'cn=t2,cn=krb5', '-sscope', 'one', + '-maxtktlife', '5min', '-maxrenewlife', '10min', '-allow_svr']) + +# Modify the realm, exercising overlapping subtree pruning. +kldaputil(['modify', '-subtrees', + 'cn=x,cn=t1,cn=krb5:cn=t1,cn=krb5:cn=t2,cn=krb5:cn=y,cn=t2,cn=krb5', + '-containerref', 'cn=t1,cn=krb5', '-sscope', 'sub', + '-maxtktlife', '5hour', '-maxrenewlife', '10hour', '+allow_svr']) + +out = kldaputil(['list']) +if out != 'KRBTEST.COM\n': + fail('Unexpected kdb5_ldap_util list output') + +# Create a principal at a specified DN. This is a little dodgy +# because we're sticking a krbPrincipalAux objectclass onto a subtree +# krbContainer, but it works and it avoids having to load core.schema +# in the test LDAP server. +out = realm.run_kadminl('ank -randkey -x dn=cn=krb5 princ1') +if 'DN is out of the realm subtree' not in out: + fail('Unexpected kadmin.local output for out-of-realm dn') +out = realm.run_kadminl('ank -randkey -x dn=cn=t2,cn=krb5 princ1') +if 'Principal "princ1@KRBTEST.COM" created.\n' not in out: + fail('Unexpected kadmin.local output for specified dn') +out = realm.run_kadminl('getprinc princ1') +if 'Principal: princ1' not in out: + fail('Unexpected kadmin.local output after creating princ1') +out = realm.run_kadminl('ank -randkey -x dn=cn=t2,cn=krb5 again') +if 'ldap object is already kerberized' not in out: + fail('Unexpected kadmin.local output trying to re-kerberize DN') +# Check that we can't set linkdn on a non-standalone object. +out = realm.run_kadminl('modprinc -x linkdn=cn=t1,cn=krb5 princ1') +if 'link information can not be set' not in out: + fail('Unexpected kadmin.local output trying to set linkdn on princ1') + +# Create a principal with a specified linkdn. +out = realm.run_kadminl('ank -randkey -x linkdn=cn=krb5 princ2') +if 'DN is out of the realm subtree' not in out: + fail('Unexpected kadmin.local output for out-of-realm linkdn') +out = realm.run_kadminl('ank -randkey -x linkdn=cn=t1,cn=krb5 princ2') +if 'Principal "princ2@KRBTEST.COM" created.\n' not in out: + fail('Unexpected kadmin.local output for specified linkdn') +# Check that we can't reset linkdn. +out = realm.run_kadminl('modprinc -x linkdn=cn=t2,cn=krb5 princ2') +if 'kerberos principal is already linked' not in out: + fail('Unexpected kadmin.local output for re-specified linkdn') + +# Create a principal with a specified containerdn. +out = realm.run_kadminl('ank -randkey -x containerdn=cn=krb5 princ3') +if 'DN is out of the realm subtree' not in out: + fail('Unexpected kadmin.local output for out-of-realm containerdn') +out = realm.run_kadminl('ank -randkey -x containerdn=cn=t1,cn=krb5 princ3') +if 'Principal "princ3@KRBTEST.COM" created.\n' not in out: + fail('Unexpected kadmin.local output for specified containerdn') +out = realm.run_kadminl('modprinc -x containerdn=cn=t2,cn=krb5 princ3') +if 'containerdn option not supported' not in out: + fail('Unexpected kadmin.local output trying to reset containerdn') + +# Create and modify a ticket policy. +kldaputil(['create_policy', '-maxtktlife', '3hour', '-maxrenewlife', '6hour', + '-allow_forwardable', 'tktpol']) +kldaputil(['modify_policy', '-maxtktlife', '4hour', '-maxrenewlife', '8hour', + '+requires_preauth', 'tktpol']) +out = kldaputil(['view_policy', 'tktpol']) +if ('Ticket policy: tktpol\n' not in out or + 'Maximum ticket life: 0 days 04:00:00\n' not in out or + 'Maximum renewable life: 0 days 08:00:00\n' not in out or + 'Ticket flags: DISALLOW_FORWARDABLE REQUIRES_PRE_AUTH' not in out): + fail('Unexpected kdb5_ldap_util view_policy output') + +out = kldaputil(['list_policy']) +if out != 'tktpol\n': + fail('Unexpected kdb5_ldap_util list_policy output') + +# Associate the ticket policy to a principal. +realm.run_kadminl('ank -randkey -x tktpolicy=tktpol princ4') +out = realm.run_kadminl('getprinc princ4') +if ('Maximum ticket life: 0 days 04:00:00\n' not in out or + 'Maximum renewable life: 0 days 08:00:00\n' not in out or + 'Attributes: DISALLOW_FORWARDABLE REQUIRES_PRE_AUTH\n' not in out): + fail('Unexpected getprinc output with ticket policy') + +# Destroying the policy should fail while a principal references it. +kldaputil(['destroy_policy', '-force', 'tktpol'], expected_code=1) + +# Dissociate the ticket policy from the principal. +realm.run_kadminl('modprinc -x tktpolicy= princ4') +out = realm.run_kadminl('getprinc princ4') +if ('Maximum ticket life: 0 days 05:00:00\n' not in out or + 'Maximum renewable life: 0 days 10:00:00\n' not in out or + 'Attributes:\n' not in out): + fail('Unexpected getprinc output without ticket policy') + +# Destroy the ticket policy. +kldaputil(['destroy_policy', '-force', 'tktpol']) +kldaputil(['view_policy', 'tktpol'], expected_code=1) +out = kldaputil(['list_policy']) +if out: + fail('Unexpected kdb5_ldap_util list_policy output after destroy') + +# Create another ticket policy to be destroyed with the realm. +kldaputil(['create_policy', 'tktpol2']) + +# Do some basic tests with a KDC against the LDAP module, exercising the +# db_args processing code. +realm.start_kdc(['-x', 'nconns=3', '-x', 'host=' + ldap_uri, + '-x', 'binddn=' + admin_dn, '-x', 'bindpwd=' + admin_pw]) +realm.addprinc(realm.user_princ, password('user')) +realm.addprinc(realm.host_princ) +realm.extract_keytab(realm.host_princ, realm.keytab) +realm.kinit(realm.user_princ, password('user')) +realm.run_as_client([kvno, realm.host_princ]) +realm.klist(realm.user_princ, realm.host_princ) +realm.stop() + +# Briefly test dump and load. +dumpfile = os.path.join(realm.testdir, 'dump') +realm.run_as_master([kdb5_util, 'dump', dumpfile]) +out = realm.run_as_master([kdb5_util, 'load', dumpfile], expected_code=1) +if 'plugin requires -update argument' not in out: + fail('Unexpected error from kdb5_util load without -update') +realm.run_as_master([kdb5_util, 'load', '-update', dumpfile]) + +# Destroy the realm. +kldaputil(['destroy', '-f']) +out = kldaputil(['list']) +if out: + fail('Unexpected kdb5_ldap_util list output after destroy') + +# We could still use tests to exercise: +# * DB arg handling in krb5_ldap_create +# * krbAllowedToDelegateTo attribute processing +# * Special character handling in ldap_filter_correct (some bugs to +# fix first, see #7296 and September 2012 krbdev discussion) +# * A load operation overwriting a standalone principal entry which +# already exists but doesn't have a krbPrincipalName attribute +# matching the principal name. +# * A bunch of invalid-input error conditions +# +# There is no coverage for the following because it would be difficult: +# * Out-of-memory error conditions +# * Handling of failures from slapd (including krb5_retry_get_ldap_handle) +# * Handling of servers which don't support mod-increment +# * krb5_ldap_delete_krbcontainer (only happens if krb5_ldap_create fails) + +success('LDAP and DB2 KDB tests') diff --git a/src/util/k5test.py b/src/util/k5test.py index 3400154ca..37e8929c3 100644 --- a/src/util/k5test.py +++ b/src/util/k5test.py @@ -194,6 +194,7 @@ Scripts may use the following functions and variables: - kadmind - kadmin - kadmin_local + - kdb5_ldap_util - kdb5_util - ktutil - kinit @@ -1238,6 +1239,8 @@ krb5kdc = os.path.join(buildtop, 'kdc', 'krb5kdc') kadmind = os.path.join(buildtop, 'kadmin', 'server', 'kadmind') kadmin = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin') kadmin_local = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin.local') +kdb5_ldap_util = os.path.join(buildtop, 'plugins', 'kdb', 'ldap', 'ldap_util', + 'kdb5_ldap_util') kdb5_util = os.path.join(buildtop, 'kadmin', 'dbutil', 'kdb5_util') ktutil = os.path.join(buildtop, 'kadmin', 'ktutil', 'ktutil') kinit = os.path.join(buildtop, 'clients', 'kinit', 'kinit') |
