From: Dax Kelson Date: Tue, 13 May 2025 17:54:41 +0000 (-0600) Subject: Add xrealmauthz KDC policy module and tests X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ae8801b8e12d198f11f9279c747f8fa6d48c593e;p=thirdparty%2Fkrb5.git 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@mit.edu: simplified code and tests; edited commit message] --- diff --git a/src/Makefile.in b/src/Makefile.in index 01fb060f73..407bfc3a00 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 4325fae992..bf6cc14c52 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 0000000000..78346d6572 --- /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 +@libnover_frag@ +@libobj_frag@ diff --git a/src/plugins/kdcpolicy/xrealmauthz/deps b/src/plugins/kdcpolicy/xrealmauthz/deps new file mode 100644 index 0000000000..4ecf533f3f --- /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 0000000000..72f077d434 --- /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@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@REALM2.COM xr:@REALM2.COM "" + * kadmin.local setstr krbtgt/REALM1.COM@REALM2.COM xr:@REALM3.COM "" + * + * 3. If the string attribute "xr:PRINC" is present in KRBTGT/MYREALM@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@REALM2.COM and u2@REALM3.COM: + * + * kadmin.local setstr krbtgt/REALM1.COM@REALM2.COM xr:u1 "" + * kadmin.local setstr krbtgt/REALM1.COM@REALM2.COM xr:u2@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 +#include + +/* 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 0000000000..a5794afdbd --- /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 41ac0d3b2a..80ac35aacc 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 0000000000..3b3921f036 --- /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@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')