krb5 commit: Add xrealmauthz KDC policy module and tests

ghudson at mit.edu ghudson at mit.edu
Mon Jun 16 17:59:01 EDT 2025


https://github.com/krb5/krb5/commit/ae8801b8e12d198f11f9279c747f8fa6d48c593e
commit ae8801b8e12d198f11f9279c747f8fa6d48c593e
Author: Dax Kelson <dakelson at redhat.com>
Date:   Tue May 13 11:54:41 2025 -0600

    Add xrealmauthz KDC policy module and tests
    
    This module provides fine-grained access control for cross-realm
    authentications by checking string attributes on the incoming
    cross-realm TGT entry.  It supports realm-based and principal-specific
    authorization rules.
    
    The module is not installed by the build system or loaded by default,
    and is documented only in the module source code.
    
    [ghudson at mit.edu: simplified code and tests; edited commit message]

 src/Makefile.in                                    |   1 +
 src/configure.ac                                   |   1 +
 src/plugins/kdcpolicy/xrealmauthz/Makefile.in      |  18 +
 src/plugins/kdcpolicy/xrealmauthz/deps             |  14 +
 src/plugins/kdcpolicy/xrealmauthz/main.c           | 380 +++++++++++++++++++++
 .../kdcpolicy/xrealmauthz/xrealmauthz.exports      |   1 +
 src/tests/Makefile.in                              |   1 +
 src/tests/t_xrealmauthz.py                         | 246 +++++++++++++
 8 files changed, 662 insertions(+)

diff --git a/src/Makefile.in b/src/Makefile.in
index 01fb060f7..407bfc3a0 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -25,6 +25,7 @@ SUBDIRS=util include lib \
 	@lmdb_plugin_dir@ \
 	plugins/kdb/test \
 	plugins/kdcpolicy/test \
+	plugins/kdcpolicy/xrealmauthz \
 	plugins/preauth/otp \
 	plugins/preauth/pkinit \
 	plugins/preauth/spake \
diff --git a/src/configure.ac b/src/configure.ac
index 4325fae99..bf6cc14c5 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -1559,6 +1559,7 @@ V5_AC_OUTPUT_MAKEFILE(.
 	plugins/kdb/db2/libdb2/test
 	plugins/kdb/test
 	plugins/kdcpolicy/test
+	plugins/kdcpolicy/xrealmauthz
 	plugins/preauth/otp
 	plugins/preauth/spake
 	plugins/preauth/test
diff --git a/src/plugins/kdcpolicy/xrealmauthz/Makefile.in b/src/plugins/kdcpolicy/xrealmauthz/Makefile.in
new file mode 100644
index 000000000..78346d657
--- /dev/null
+++ b/src/plugins/kdcpolicy/xrealmauthz/Makefile.in
@@ -0,0 +1,18 @@
+mydir=plugins$(S)kdcpolicy$(S)xrealmauthz
+BUILDTOP=$(REL)..$(S)..$(S)..
+
+LIBBASE=xrealmauthz
+LIBMAJOR=0
+LIBMINOR=0
+RELDIR=../plugins/kdcpolicy/xrealmauthz
+SHLIB_EXPDEPS=$(KRB5_BASE_DEPLIBS) $(KDB5_DEPLIB)
+SHLIB_EXPLIBS=$(KRB5_BASE_LIBS) $(KDB5_LIB)
+STLIBOBJS=main.o
+
+SRCS=$(srcdir)/main.c
+
+all-unix: all-libs
+install-unix:
+clean-unix:: clean-libs clean-libobjs
+ at libnover_frag@
+ at libobj_frag@
diff --git a/src/plugins/kdcpolicy/xrealmauthz/deps b/src/plugins/kdcpolicy/xrealmauthz/deps
new file mode 100644
index 000000000..4ecf533f3
--- /dev/null
+++ b/src/plugins/kdcpolicy/xrealmauthz/deps
@@ -0,0 +1,14 @@
+#
+# Generated makefile dependencies follow.
+#
+main.so main.po $(OUTPRE)main.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+  $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+  $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+  $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+  $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+  $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+  $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+  $(top_srcdir)/include/kdb.h $(top_srcdir)/include/krb5.h \
+  $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/kdcpolicy_plugin.h \
+  $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+  $(top_srcdir)/include/socket-utils.h main.c
diff --git a/src/plugins/kdcpolicy/xrealmauthz/main.c b/src/plugins/kdcpolicy/xrealmauthz/main.c
new file mode 100644
index 000000000..72f077d43
--- /dev/null
+++ b/src/plugins/kdcpolicy/xrealmauthz/main.c
@@ -0,0 +1,380 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/kdcpolicy/xrealmauthz/main.c - xrealmauthz module implementation */
+/*
+ * Copyright (C) 2025 by Red Hat, Inc.
+ * 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.
+ */
+
+/*
+ * The xrealmauthz module restricts authentications from clients in other
+ * realms.  It is not installed by the build system or loaded by default.  It
+ * can be loaded with the following configuration:
+ *
+ *   [plugins]
+ *     kdcpolicy = {
+ *       module = /path/to/xrealmauthz.so
+ *     }
+ *
+ * Once the module is loaded, all authentications from clients in other realms
+ * are rejected unless they are explicitly authorized, unless enforcement is
+ * turned off.  Authorization can be achieved in three ways:
+ *
+ * 1. If the xrealmauthz_allowed_realms profile variable in [kdcdefaults] has
+ *    one or more values, authentications by clients in those realms are always
+ *    permitted by this module, regardless of the authentication path.  (The
+ *    authentication path must still pass the transited check as configured in
+ *    [capaths]).  For example, the following configuration:
+ *
+ *    [kdcdefaults]
+ *      xrealmauthz_allowed_realms = REALM2.COM
+ *      xrealmauthz_allowed_realms = REALM3.COM
+ *
+ *    would cause this module to permit all authentications from clients in
+ *    REALM2.COM or REALM3.COM.
+ *
+ * 2. If the string attribute "xr:@CLIENTREALM" is present in the TGS entry
+ *    krbtgt/MYREALM at OREALM (where MYREALM is the realm served by the KDC),
+ *    then authentications from clients in CLIENTREALM are permitted via
+ *    OREALM.  The value of the string attribute is ignored.  For example, if
+ *    this KDC serves REALM1.COM, the following commands would permit
+ *    authentications via REALM2.COM for clients in both REALM2.COM itself and
+ *    REALM3.COM:
+ *
+ *      kadmin.local setstr krbtgt/REALM1.COM at REALM2.COM xr:@REALM2.COM ""
+ *      kadmin.local setstr krbtgt/REALM1.COM at REALM2.COM xr:@REALM3.COM ""
+ *
+ * 3. If the string attribute "xr:PRINC" is present in KRBTGT/MYREALM at OREALM,
+ *    authentications from the client principal PRINC are permitted.  PRINC
+ *    must contain a realm part if its realm differs from OREALM, and must
+ *    _not_ contain a realm part if its realm is the same as OREALM.  For
+ *    example, the following commands would permit authentications via
+ *    REALM2.COM for the clients u1 at REALM2.COM and u2 at REALM3.COM:
+ *
+ *      kadmin.local setstr krbtgt/REALM1.COM at REALM2.COM xr:u1 ""
+ *      kadmin.local setstr krbtgt/REALM1.COM at REALM2.COM xr:u2 at REALM3.COM ""
+ *
+ * Enforcement may be turned off by setting the profile variable
+ * xrealmauthz_enforcing to false in [kdcdefaults]:
+ *
+ *   [kdcdefaults]
+ *     xrealmauthz_enforcing = false
+ *
+ * If enforcement is turned off, this module will permit all cross-realm
+ * authentications, but will log authentications that would otherwise be denied
+ * with a message containing:
+ *
+ *   xrealmauthz module would deny CLIENTPRINC for SERVERPRINC from REALM
+ */
+
+#include "k5-int.h"
+#include <kdb.h>
+#include <krb5/kdcpolicy_plugin.h>
+
+/* Prefix used for cross-realm authorization attributes */
+#define ATTR_PREFIX "xr:"
+
+struct xrealmauthz_data {
+    int enforcing;  /* Whether to actually enforce restrictions */
+    krb5_data *allowed_realms;
+    size_t num_allowed_realms;
+};
+
+/* Typedef for pointer to the structure */
+typedef struct xrealmauthz_data *xrealmauthz_moddata;
+
+static void
+free_moddata(xrealmauthz_moddata data)
+{
+    size_t i;
+
+    if (data == NULL)
+        return;
+    for (i = 0; i < data->num_allowed_realms; i++)
+        free(data->allowed_realms[i].data);
+    free(data->allowed_realms);
+    free(data);
+}
+
+static krb5_error_code
+xrealmauthz_init(krb5_context context, krb5_kdcpolicy_moddata *moddata_out)
+{
+    krb5_error_code ret;
+    int enforcing = 1;
+    xrealmauthz_moddata data = NULL;
+    profile_t profile = NULL;
+    char **realmlist = NULL;
+    size_t count, i;
+    const char *section[] = { "kdcdefaults", "xrealmauthz_allowed_realms",
+                              NULL };
+
+    *moddata_out = NULL;
+
+    ret = krb5_get_profile(context, &profile);
+    if (ret)
+        goto cleanup;
+
+    /* Check if enforcing mode is disabled in config, default to TRUE */
+    profile_get_boolean(profile, "kdcdefaults", "xrealmauthz_enforcing",
+                        NULL, TRUE, &enforcing);
+
+    data = k5alloc(sizeof(*data), &ret);
+    if (data == NULL)
+        goto cleanup;
+
+    /* Get array of allowed realms from config. */
+    ret = profile_get_values(profile, section, &realmlist);
+    if (ret && ret != PROF_NO_RELATION)
+        goto cleanup;
+    ret = 0;
+
+    if (realmlist != NULL) {
+        /* Count and allocate realm entries. */
+        for (count = 0; realmlist[count] != NULL; count++);
+        data->allowed_realms = k5calloc(count, sizeof(krb5_data), &ret);
+        if (data->allowed_realms == NULL)
+            goto cleanup;
+        data->num_allowed_realms = count;
+
+        /* Transfer ownership of the strings from the profile list. */
+        for (i = 0; i < count; i++)
+            data->allowed_realms[i] = string2data(realmlist[i]);
+        free(realmlist);
+        realmlist = NULL;
+    }
+
+    data->enforcing = enforcing;
+
+    com_err("", 0,
+            _("xrealmauthz cross-realm authorization module loaded "
+              "(enforcing mode: %s, pre-approved realms: %d)"),
+            enforcing ? _("enabled") : _("disabled"),
+            (int)data->num_allowed_realms);
+
+    *moddata_out = (krb5_kdcpolicy_moddata)data;
+    data = NULL;
+
+cleanup:
+    free_moddata(data);
+    profile_free_list(realmlist);
+    profile_release(profile);
+    return ret;
+}
+
+static krb5_error_code
+xrealmauthz_fini(krb5_context context, krb5_kdcpolicy_moddata moddata)
+{
+    free_moddata((xrealmauthz_moddata)moddata);
+    return 0;
+}
+
+static krb5_boolean
+is_realm_preapproved(xrealmauthz_moddata data, const krb5_data *client_realm)
+{
+    size_t i;
+
+    for (i = 0; i < data->num_allowed_realms; i++) {
+        if (data_eq(data->allowed_realms[i], *client_realm))
+            return TRUE;
+    }
+    return FALSE;
+}
+
+/* Set *result_out to true if tgt has a string attribute for attr_key with any
+ * value. */
+static krb5_error_code
+check_attr(krb5_context context, krb5_db_entry *tgt, const char *key,
+           krb5_boolean *result_out)
+{
+    krb5_error_code ret;
+    char *value;
+
+    *result_out = FALSE;
+
+    ret = krb5_dbe_get_string(context, tgt, key, &value);
+    if (!ret && value != NULL) {
+        *result_out = TRUE;
+        krb5_dbe_free_string(context, value);
+    }
+
+    return ret;
+}
+
+/* Set *result_out to true if tgt has an ACL attribute for realm
+ * ("xr:@realm"). */
+static krb5_error_code
+check_realm_attr(krb5_context context, krb5_db_entry *tgt,
+                 const krb5_data *realm, krb5_boolean *result_out)
+{
+    krb5_error_code ret;
+    char *key;
+
+    if (asprintf(&key, "%s@%.*s", ATTR_PREFIX,
+                 (int)realm->length, realm->data) < 0)
+        return ENOMEM;
+    ret = check_attr(context, tgt, key, result_out);
+    free(key);
+    return ret;
+}
+
+/* Set *result_out to true if tgt has an ACL attribute for princ ("xr:princ",
+ * with the realm omitted if princ is in tgt's realm). */
+static krb5_error_code
+check_princ_attr(krb5_context context, krb5_db_entry *tgt,
+                 krb5_const_principal princ, krb5_boolean *result_out)
+{
+    krb5_error_code ret;
+    int flags = 0, r;
+    char *princstr, *key;
+
+    /* Omit the realm if princ is in tgt's realm. */
+    if (krb5_realm_compare(context, tgt->princ, princ))
+        flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM;
+    ret = krb5_unparse_name_flags(context, princ, flags, &princstr);
+    if (ret)
+        return ret;
+
+    r = asprintf(&key, "%s%s", ATTR_PREFIX, princstr);
+    krb5_free_unparsed_name(context, princstr);
+    if (r < 0)
+        return ENOMEM;
+
+    ret = check_attr(context, tgt, key, result_out);
+    free(key);
+    return ret;
+}
+
+/* Check if cross-realm authentication is allowed from client via tgtname. */
+static krb5_error_code
+check_cross_realm_auth(krb5_context context, krb5_const_principal client,
+                       krb5_const_principal tgtname,
+                       krb5_const_principal server, xrealmauthz_moddata data,
+                       const char **status_out)
+{
+    krb5_error_code ret;
+    char *cpstr = NULL, *spstr = NULL;
+    krb5_boolean is_allowed = FALSE;
+    krb5_db_entry *tgt_entry = NULL;
+
+    *status_out = NULL;
+
+    /* Check if the client realm is pre-approved. */
+    if (is_realm_preapproved(data, &client->realm))
+        return 0;
+
+    /* Get TGT principal entry for string attribute checks. */
+    ret = krb5_db_get_principal(context, tgtname, 0, &tgt_entry);
+    if (ret) {
+        *status_out = "XREALMAUTHZ_GET_TGT";
+        goto cleanup;
+    }
+
+    /* Check if client's realm is allowed. */
+    ret = check_realm_attr(context, tgt_entry, &client->realm, &is_allowed);
+    if (ret || is_allowed)
+        goto cleanup;
+
+    /* Check if client is allowed. */
+    ret = check_princ_attr(context, tgt_entry, client, &is_allowed);
+    if (ret || is_allowed)
+        goto cleanup;
+
+    if (data->enforcing) {
+        /* The authentication is denied.  KDC logging of the error will include
+         * the client and server principal names. */
+        *status_out = "XREALMAUTHZ";
+        ret = KRB5KDC_ERR_POLICY;
+        k5_setmsg(context, ret, _("xrealmauthz module denied from %.*s"),
+                  (int)tgtname->realm.length, tgtname->realm.data);
+        goto cleanup;
+    }
+
+    /* The authentication would be denied if enforcement were turned on.
+     * Generate a log message including the client and server names. */
+    ret = krb5_unparse_name(context, client, &cpstr);
+    if (ret)
+        goto cleanup;
+    ret = krb5_unparse_name(context, server, &spstr);
+    if (ret)
+        goto cleanup;
+    com_err("", 0, _("xrealmauthz module would deny %s for %s from %.*s"),
+            cpstr, spstr, (int)tgtname->realm.length, tgtname->realm.data);
+
+cleanup:
+    krb5_db_free_principal(context, tgt_entry);
+    krb5_free_unparsed_name(context, cpstr);
+    krb5_free_unparsed_name(context, spstr);
+    return ret;
+}
+
+static krb5_error_code
+xrealmauthz_check(krb5_context context, krb5_kdcpolicy_moddata moddata,
+                  const krb5_kdc_req *request,
+                  const struct _krb5_db_entry_new *server,
+                  const krb5_ticket *ticket,
+                  const char *const *auth_indicators, const char **status_out,
+                  krb5_deltat *lifetime_out, krb5_deltat *renew_lifetime_out)
+{
+    xrealmauthz_moddata data = (xrealmauthz_moddata)moddata;
+
+    *status_out = NULL;
+    *lifetime_out = *renew_lifetime_out = 0;
+
+    /* Only check cross-realm requests. */
+    if (krb5_realm_compare(context, server->princ, ticket->enc_part2->client))
+        return 0;
+
+    /* Don't check if the header ticket isn't a TGT (such as for renewals). */
+    if (ticket->server->length != 2 ||
+        !data_eq_string(ticket->server->data[0], KRB5_TGS_NAME))
+        return 0;
+
+    return check_cross_realm_auth(context, ticket->enc_part2->client,
+                                  ticket->server, request->server, data,
+                                  status_out);
+}
+
+krb5_error_code
+kdcpolicy_xrealmauthz_initvt(krb5_context context, int maj_ver, int min_ver,
+                             krb5_plugin_vtable vtable);
+
+krb5_error_code
+kdcpolicy_xrealmauthz_initvt(krb5_context context, int maj_ver, int min_ver,
+                             krb5_plugin_vtable vtable)
+{
+    krb5_kdcpolicy_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+
+    vt = (krb5_kdcpolicy_vtable)vtable;
+    vt->name = "xrealmauthz";
+    vt->init = xrealmauthz_init;
+    vt->fini = xrealmauthz_fini;
+    vt->check_tgs = xrealmauthz_check;
+    return 0;
+}
diff --git a/src/plugins/kdcpolicy/xrealmauthz/xrealmauthz.exports b/src/plugins/kdcpolicy/xrealmauthz/xrealmauthz.exports
new file mode 100644
index 000000000..a5794afdb
--- /dev/null
+++ b/src/plugins/kdcpolicy/xrealmauthz/xrealmauthz.exports
@@ -0,0 +1 @@
+kdcpolicy_xrealmauthz_initvt
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 41ac0d3b2..80ac35aac 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -193,6 +193,7 @@ check-pytests: responder s2p s4u2proxy unlockiter s4u2self
 	$(RUNPYTEST) $(srcdir)/t_replay.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_sendto_kdc.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_alias.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_xrealmauthz.py $(PYTESTFLAGS)
 
 clean:
 	$(RM) adata conccache etinfo forward gcred hist hooks hrealm
diff --git a/src/tests/t_xrealmauthz.py b/src/tests/t_xrealmauthz.py
new file mode 100644
index 000000000..3b3921f03
--- /dev/null
+++ b/src/tests/t_xrealmauthz.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+
+from k5test import *
+import os
+
+# Define realm names for testing topology.
+REALM1 = 'REALM1.COM'
+REALM2 = 'REALM2.COM'
+REALM3 = 'REALM3.COM'
+
+# Name the cross-realm TGS for incoming authentications as seen by REALM1.
+cross_tgt_name = 'krbtgt/REALM1.COM at REALM2.COM'
+
+# Define capaths configuration to allow authentication from REALM3 via REALM2.
+capaths_config = {
+    'capaths': {
+        REALM3: {REALM1: [REALM2]},  # REALM3 -> REALM2 -> REALM1
+        REALM2: {REALM1: '.'}        # Direct path from REALM2 to REALM1
+    }
+}
+
+# Restart realm's KDC with xrealmauthz_enforcing set to true, false,
+# or not set at all if enforcing is None.  Clear the log and look for
+# the expected startup message.
+def set_enforcing_mode(realm, enforcing):
+    if enforcing is None:
+        kdc_conf = {}
+    else:
+        kdc_conf = {'kdcdefaults': {'xrealmauthz_enforcing': str(enforcing)}}
+    expected_msg = 'enabled' if enforcing else 'disabled'
+
+    realm.stop_kdc()
+    realm_env = realm.special_env('enforce_config', True, kdc_conf=kdc_conf)
+
+    # Clear the KDC log before starting.
+    kdc_log = os.path.join(realm.testdir, 'kdc.log')
+    with open(kdc_log, 'w') as f:
+        f.truncate(0)
+
+    realm.start_kdc(env=realm_env)
+
+    # Check for module initialization message.
+    with open(kdc_log, 'r') as f:
+        log_content = f.read()
+        expected_init_msg = 'loaded (enforcing mode: %s,' % expected_msg
+        if expected_init_msg not in log_content:
+            fail('could not find module init log message')
+
+
+# Return true if a "would deny" message is present in the KDC log file.
+def check_would_deny_log(realm):
+    kdc_log = os.path.join(realm.testdir, 'kdc.log')
+    with open(kdc_log, 'r') as f:
+        log_content = f.read()
+        return 'would deny' in log_content
+
+
+# Clear the KDC log file.
+def clear_kdc_log(realm):
+    kdc_log = os.path.join(realm.testdir, 'kdc.log')
+    with open(kdc_log, 'w') as f:
+        f.truncate(0)
+
+
+# Return a descriptive string for an enforcing mode.
+def enforcing_str(enforcing):
+    if enforcing is None:
+        return 'default mode'
+    elif enforcing:
+        return 'enforcing explicitly enabled'
+    else:
+        return 'enforcing explicitly disabled'
+
+
+# Test unauthorized cross-realm access with the given enforcing mode.
+def test_denied(src_realm, dst_realm, client_princ, service_princ,
+                enforcing=None):
+    src_realm.kinit(client_princ, password('user'))
+    if enforcing is False:
+        clear_kdc_log(dst_realm)
+        src_realm.run([kvno, service_princ])
+        if not check_would_deny_log(dst_realm):
+            fail('Expected "would deny" message in KDC log')
+    else:
+        # Both enforcing=True and enforcing=None should enforce.
+        src_realm.run([kvno, service_princ], expected_code=1,
+                      expected_msg='KDC policy rejects request')
+
+
+# Verify that access is allowed when properly authorized.
+def test_allowed(src_realm, client_princ, service_princ):
+    src_realm.kinit(client_princ, password('user'))
+    src_realm.run([kvno, service_princ])
+
+
+# Test realm-based authorization with direct trust.
+def test_direct_realm_authz(r1, r2, enforcing=None):
+    mark('direct realm authorization (%s)' % enforcing_str(enforcing))
+
+    # Verify that access is denied without authorization.
+    test_denied(r2, r1, r2.user_princ, r1.host_princ, enforcing)
+
+    # Add realm authorization and verify that access is allowed.
+    r1.run([kadminl, 'setstr', cross_tgt_name, 'xr:@' + r2.realm, '""'])
+    test_allowed(r2, r2.user_princ, r1.host_princ)
+
+    # Remove authorization and verify denial/logging again.
+    r1.run([kadminl, 'delstr', cross_tgt_name, 'xr:@' + r2.realm])
+    test_denied(r2, r1, r2.user_princ, r1.host_princ, enforcing)
+
+
+# Test principal-specific authorization with direct trust
+def test_direct_principal_authz(r1, r2, enforcing=None):
+    mark('direct princ authorization (%s)' % enforcing_str(enforcing))
+
+    # Create test principals.
+    authorized_princ = 'authz_test@' + r2.realm
+    unauthorized_princ = 'unauth_test@' + r2.realm
+    r2.addprinc(authorized_princ, password('user'))
+    r2.addprinc(unauthorized_princ, password('user'))
+
+    # Add principal authorization and verify that only
+    # authorized_princ has access.
+    r1.run([kadminl, 'setstr', cross_tgt_name, 'xr:authz_test', '""'])
+    test_allowed(r2, authorized_princ, r1.host_princ)
+    test_denied(r2, r1, unauthorized_princ, r1.host_princ, enforcing)
+
+    # Remove authorization and verify that authorized_princ is denied.
+    r1.run([kadminl, 'delstr', cross_tgt_name, 'xr:authz_test'])
+    test_denied(r2, r1, authorized_princ, r1.host_princ, enforcing)
+
+    # Clean up.
+    r2.run([kadminl, 'delprinc', '-force', authorized_princ])
+    r2.run([kadminl, 'delprinc', '-force', unauthorized_princ])
+
+
+# Test realm-based authorization with transitive trust.
+def test_transitive_realm_authz(r1, r2, r3, enforcing=None):
+    mark('transitive realm authorization (%s)' + enforcing_str(enforcing))
+
+    # Verify that access is denied/logged without authorization.
+    test_denied(r3, r1, r3.user_princ, r1.host_princ, enforcing)
+
+    # Add realm authorization and verify that access is allowed.
+    r1.run([kadminl, 'setstr', cross_tgt_name, 'xr:@' + r3.realm, '""'])
+    test_allowed(r3, r3.user_princ, r1.host_princ)
+
+    # Remove authorization and verify denial/logging again.
+    r1.run([kadminl, 'delstr', cross_tgt_name, 'xr:@' + r3.realm])
+    test_denied(r3, r1, r3.user_princ, r1.host_princ, enforcing)
+
+
+# Test principal-specific authorization with transitive trust.
+def test_transitive_principal_authz(r1, r2, r3, enforcing=None):
+    mark('transitive princ authorization (%s)' % enforcing_str(enforcing))
+
+    # Create test principals.
+    authorized_princ = 'authz_test@' + r3.realm
+    unauthorized_princ = 'unauth_test@' + r3.realm
+    r3.addprinc(authorized_princ, password('user'))
+    r3.addprinc(unauthorized_princ, password('user'))
+
+    # Add principal authorization and verify that only
+    # authorized_princ has access.
+    r1.run([kadminl, 'setstr', cross_tgt_name, 'xr:' + authorized_princ, '""'])
+    test_allowed(r3, authorized_princ, r1.host_princ)
+    test_denied(r3, r1, unauthorized_princ, r1.host_princ, enforcing)
+
+    # Remove authorization and verify that authorized_princ is denied.
+    r1.run([kadminl, 'delstr', cross_tgt_name, 'xr:' + authorized_princ])
+    test_denied(r3, r1, authorized_princ, r1.host_princ, enforcing)
+
+    # Clean up.
+    r3.run([kadminl, 'delprinc', '-force', authorized_princ])
+    r3.run([kadminl, 'delprinc', '-force', unauthorized_princ])
+
+
+# Test pre-approved realms configuration.
+def test_allowed_realms(r1, r2, r3, enforcing=None):
+    mark('pre-approved realms (%s)' % enforcing_str(enforcing))
+
+    # Configure a single allowed realm.
+    conf = {'kdcdefaults': {'xrealmauthz_allowed_realms': [REALM2]}}
+    if enforcing is not None:
+        conf['kdcdefaults']['xrealmauthz_enforcing'] = str(enforcing)
+    r1.stop_kdc()
+    realm_env = r1.special_env('allowed_realms', True, kdc_conf=conf)
+    r1.start_kdc(env=realm_env)
+
+    # Verify that REALM2 has full access, but REALM3 still goes
+    # through normal authorization and is denied.
+    test_allowed(r2, r2.user_princ, r1.host_princ)
+    test_denied(r3, r1, r3.user_princ, r1.host_princ, enforcing)
+
+    # Configure multiple allowed realms.
+    conf = {'kdcdefaults': {'xrealmauthz_allowed_realms': [REALM2, REALM3]}}
+    if enforcing is not None:
+        conf['kdcdefaults']['xrealmauthz_enforcing'] = str(enforcing)
+    r1.stop_kdc()
+    realm_env = r1.special_env('multi_allowed', True, kdc_conf=conf)
+    r1.start_kdc(env=realm_env)
+
+    # Verify that both realms have full access.
+    test_allowed(r2, r2.user_princ, r1.host_princ)
+    test_allowed(r3, r3.user_princ, r1.host_princ)
+
+
+# Configure realm1 with the xrealmauthz module enabled.
+plugin_path = os.path.join(buildtop, 'plugins', 'kdcpolicy', 'xrealmauthz',
+                           'xrealmauthz.so')
+realm1_kdc_conf = {'plugins': {'kdcpolicy':
+                               {'module': 'xrealmauthz:' + plugin_path}}}
+
+# Set up three realms for all tests.
+# REALM1 <- REALM2 <- REALM3 for transitive tests
+# REALM1 <- REALM2 direct trust is used for direct tests
+mark('creating realms')
+realms = cross_realms(3, xtgts=((1, 0), (2, 1)),
+                      args=({'realm': REALM1, 'krb5_conf': capaths_config,
+                             'kdc_conf': realm1_kdc_conf},
+                            {'realm': REALM2, 'krb5_conf': capaths_config},
+                            {'realm': REALM3, 'krb5_conf': capaths_config}))
+r1, r2, r3 = realms
+
+test_direct_realm_authz(r1, r2)
+test_direct_principal_authz(r1, r2)
+test_transitive_realm_authz(r1, r2, r3)
+test_transitive_principal_authz(r1, r2, r3)
+
+test_allowed_realms(r1, r2, r3)
+test_allowed_realms(r1, r2, r3, enforcing=True)
+test_allowed_realms(r1, r2, r3, enforcing=False)
+
+set_enforcing_mode(r1, True)
+test_direct_realm_authz(r1, r2, enforcing=True)
+test_direct_principal_authz(r1, r2, enforcing=True)
+test_transitive_realm_authz(r1, r2, r3, enforcing=True)
+test_transitive_principal_authz(r1, r2, r3, enforcing=True)
+
+set_enforcing_mode(r1, False)
+test_direct_realm_authz(r1, r2, enforcing=False)
+test_direct_principal_authz(r1, r2, enforcing=False)
+test_transitive_realm_authz(r1, r2, r3, enforcing=False)
+test_transitive_principal_authz(r1, r2, r3, enforcing=False)
+
+success('Cross-realm authorization tests completed successfully')


More information about the cvs-krb5 mailing list