--- /dev/null
+#!/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.')
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).
"""
import atexit
+import itertools
import optparse
import os
import shlex
_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' : {