From 97eb9f3d7c26364b137f207b431b5c1f67691225 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Thu, 3 Nov 2011 17:27:59 +0000 Subject: [PATCH] Add cross-realm tests to python test framework Add a cross_realms function to k5test.py to generate several linked realms. Add a test script t_crossrealm.py to exercise six different cross-realm scenarios. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25429 dc483132-0cff-0310-8789-dd5450dbe970 --- src/tests/Makefile.in | 1 + src/tests/t_crossrealm.py | 113 ++++++++++++++++++++++++++++++++++++++ src/util/k5test.py | 78 ++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 src/tests/t_crossrealm.py diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index 4709cea25..01d70fb25 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -70,6 +70,7 @@ check-pytests:: $(RUNPYTEST) $(srcdir)/t_renprinc.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_stringattr.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_crossrealm.py $(PYTESTFLAGS) # $(RUNPYTEST) $(srcdir)/kdc_realm/kdcref.py $(PYTESTFLAGS) clean:: diff --git a/src/tests/t_crossrealm.py b/src/tests/t_crossrealm.py new file mode 100644 index 000000000..3a23b7976 --- /dev/null +++ b/src/tests/t_crossrealm.py @@ -0,0 +1,113 @@ +#!/usr/bin/python + +# Copyright (C) 2011 by the Massachusetts Institute of Technology. +# All rights reserved. +# +# Export of this software from the United States of America may +# require a specific license from the United States Government. +# It is the responsibility of any person or organization contemplating +# export to obtain such a license before exporting. +# +# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and +# distribute this software and its documentation for any purpose and +# without fee is hereby granted, provided that the above copyright +# notice appear in all copies and that both that copyright notice and +# this permission notice appear in supporting documentation, and that +# the name of M.I.T. not be used in advertising or publicity pertaining +# to distribution of the software without specific, written prior +# permission. Furthermore if you modify this software you must label +# your software as modified software and not distribute it in such a +# fashion that it might be confused with the original M.I.T. software. +# M.I.T. makes no representations about the suitability of +# this software for any purpose. It is provided "as is" without express +# or implied warranty. + +from k5test import * + +def test_kvno(r, princ, test): + output = r.run_as_client([kvno, princ]) + if princ not in output: + fail('%s: principal %s not in kvno output' % (test, princ)) + + +def stop(*realms): + for r in realms: + r.stop() + + +# Basic two-realm test with cross TGTs in both directions. +r1, r2 = cross_realms(2, start_kadmind=False) +test_kvno(r1, r2.host_princ, 'basic r1->r2') +test_kvno(r2, r1.host_princ, 'basic r2->r1') +stop(r1, r2) + +# Test the KDC domain walk for hierarchically arranged realms. The +# client in A.X will ask for a cross TGT to B.X, but A.X's KDC only +# has a TGT for the intermediate realm X, so it will return that +# instead. The client will use that to get a TGT for B.X. +r1, r2, r3 = cross_realms(3, xtgts=((0,1), (1,2)), + args=({'realm': 'A.X'}, {'realm': 'X'}, + {'realm': 'B.X'}), + start_kadmind=False) +test_kvno(r1, r3.host_princ, 'KDC domain walk') +stop(r1, r2, r3) + +# Test client capaths. The client in A will ask for a cross TGT to D, +# but A's KDC won't have it and won't know an intermediate to return. +# The client will walk its A->D capaths to get TGTs for B, then C, +# then D. The KDCs for C and D need capaths settings to avoid failing +# transited checks, including a capaths for A->C. +capaths = {'capaths': {'A': {'D': ['B', 'C'], 'C': 'B'}}} +r1, r2, r3, r4 = cross_realms(4, xtgts=((0,1), (1,2), (2,3)), + args=({'realm': 'A', + 'krb5_conf': {'client': capaths}}, + {'realm': 'B'}, + {'realm': 'C', + 'krb5_conf': {'master': capaths}}, + {'realm': 'D', + 'krb5_conf': {'master': capaths}}), + start_kadmind=False) +test_kvno(r1, r4.host_princ, 'client capaths') + +# Test KDC capaths. The KDCs for A and B have appropriate capaths +# settings to determine intermediate TGTs to return, but the client +# has no idea. +capaths = {'capaths': {'A': {'D': ['B', 'C'], 'C': 'B'}, 'B': {'D': 'C'}}} +conf = {'master': capaths} +r1, r2, r3, r4 = cross_realms(4, xtgts=((0,1), (1,2), (2,3)), + args=({'realm': 'A', 'krb5_conf': conf}, + {'realm': 'B', 'krb5_conf': conf}, + {'realm': 'C', 'krb5_conf': conf}, + {'realm': 'D', 'krb5_conf': conf}), + start_kadmind=False) +test_kvno(r1, r4.host_princ, 'KDC capaths') + +# Test transited error. The KDC for C does not recognize B as an +# intermediate realm for A->C, so it refuses to issue a service +# ticket. +capaths = {'capaths': {'A': {'C': 'B'}}} +r1, r2, r3 = cross_realms(3, xtgts=((0,1), (1,2)), + args=({'realm': 'A', + 'krb5_conf': {'client': capaths}}, + {'realm': 'B'}, {'realm': 'C'}), + start_kadmind=False) +output = r1.run_as_client([kvno, r3.host_princ], expected_code=1) +if 'KDC policy rejects request' not in output: + fail('transited 1: Expected error message not in output') + +# Test a different kind of transited error. The KDC for D does not +# recognize B as an intermediate realm for A->C, so it refuses to +# verify the krbtgt/C@B ticket in the TGS AP-REQ. +capaths = {'capaths': {'A': {'D': ['B', 'C'], 'C': 'B'}, 'B': {'D': 'C'}}} +conf = {'master': capaths} +r1, r2, r3, r4 = cross_realms(4, xtgts=((0,1), (1,2), (2,3)), + args=({'realm': 'A', 'krb5_conf': conf}, + {'realm': 'B', 'krb5_conf': conf}, + {'realm': 'C', 'krb5_conf': conf}, + {'realm': 'D'}), + start_kadmind=False) +output = r1.run_as_client([kvno, r4.host_princ], expected_code=1) +if 'Illegal cross-realm ticket' not in output: + fail('transited 2: Expected error message not in output') + +success('Cross-realm tests.') diff --git a/src/util/k5test.py b/src/util/k5test.py index f79128783..8f952c8a9 100644 --- a/src/util/k5test.py +++ b/src/util/k5test.py @@ -154,6 +154,19 @@ Scripts may use the following functions and variables: honored. If keywords contains krb5_conf and/or kdc_conf fragments, they will be merged with the default and per-pass specifications. +* cross_realms(num, xtgts=None, args=None, **keywords): This function + returns a list of num realms, where each realm's configuration knows + how to contact all of the realms. By default, each realm will + contain cross TGTs in both directions for all other realms; this + default may be overridden by specifying a collection of tuples in + the xtgts parameter, where each tuple is a pair of zero-based realm + indexes, indicating that the first realm can authenticate to the + second (i.e. krbtgt/secondrealm@firstrealm exists in both realm's + databases). If args is given, it should be a list of keyword + arguments specific to each realm; these will be merged with the + global keyword arguments passed to cross_realms, with specific + arguments taking priority. + * buildtop: The top of the build directory (absolute path). * srctop: The top of the source directory (absolute path). @@ -301,6 +314,7 @@ command-line flags. These are documented in the --help output. """ import atexit +import itertools import optparse import os import shlex @@ -934,6 +948,70 @@ def multipass_realms(**keywords): _current_pass = None +def cross_realms(num, xtgts=None, args=None, **keywords): + # Build keyword args for each realm. + realm_args = [] + for i in range(num): + realmnumber = i + 1 + # Start with any global keyword arguments to this function. + a = keywords.copy() + if args and args[i]: + # Merge in specific arguments for this realm. Use + # _cfg_merge for config fragments. + a.update(args[i]) + for cf in ('krb5_conf', 'kdc_conf'): + if cf in keywords and cf in args[i]: + a[cf] = _cfg_merge(keywords[cf], args[i][cf]) + # Set defaults for the realm name, testdir, and portbase. + if not 'realm' in a: + a['realm'] = 'KRBTEST%d.COM' % realmnumber + if not 'testdir' in a: + a['testdir'] = os.path.join('testdir', str(realmnumber)) + if not 'portbase' in a: + a['portbase'] = 61000 + 10 * realmnumber + realm_args.append(a) + + # Build a [realms] config fragment containing all of the realms. + realmsection = { '$realm' : None } + for a in realm_args: + name = a['realm'] + portbase = a['portbase'] + realmsection[name] = { + 'kdc' : '$hostname:%d' % portbase, + 'admin_server' : '$hostname:%d' % (portbase + 1), + 'kpasswd_server' : '$hostname:%d' % (portbase + 2) + } + realmscfg = { 'all' : { 'realms' : realmsection } } + + # Set realmsection in each realm's krb5_conf keyword argument. + for a in realm_args: + a['krb5_conf'] = _cfg_merge(realmscfg, a.get('krb5_conf')) + + if xtgts is None: + # Default to cross tgts for every pair of realms. + xtgts = frozenset(itertools.permutations(range(num), 2)) + + # Create the realms. + realms = [] + for i in range(num): + r = K5Realm(**realm_args[i]) + # Create specified cross TGTs in this realm's db. + for j in range(num): + if j == i: + continue + iname = r.realm + jname = realm_args[j]['realm'] + if (i, j) in xtgts: + # This realm can authenticate to realm j. + r.addprinc('krbtgt/%s' % jname, password('cr-%d-%d-' % (i, j))) + if (j, i) in xtgts: + # Realm j can authenticate to this realm. + r.addprinc('krbtgt/%s@%s' % (iname, jname), + password('cr-%d-%d-' % (j, i))) + realms.append(r) + return realms + + _default_krb5_conf = { 'all' : { 'libdefaults' : { -- 2.26.2