Add cross-realm tests to python test framework
authorGreg Hudson <ghudson@mit.edu>
Thu, 3 Nov 2011 17:27:59 +0000 (17:27 +0000)
committerGreg Hudson <ghudson@mit.edu>
Thu, 3 Nov 2011 17:27:59 +0000 (17:27 +0000)
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
src/tests/t_crossrealm.py [new file with mode: 0644]
src/util/k5test.py

index 4709cea25df7d43d9c5a26859ba94180077b28c1..01d70fb255b652aa43ec54a3e475de4cbf63027e 100644 (file)
@@ -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 (file)
index 0000000..3a23b79
--- /dev/null
@@ -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.')
index f79128783040b011221fe9f1b0bce7e80c28d4c5..8f952c8a9e4075fd450916bc26d627658b541b11 100644 (file)
@@ -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' : {