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