10000 Add xrealmauthz KDC policy plugin for cross-realm authorization by dkelson · Pull Request #1431 · krb5/krb5 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add xrealmauthz KDC policy plugin for cross-realm authorization #1431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 16, 2025

Conversation

dkelson
Copy link
@dkelson dkelson commented May 13, 2025

This PR introduces a new KDC policy plugin (xrealmauthz) that provides fine-grained authorization control for cross-realm authentication in MIT Kerberos. The plugin allows administrators to explicitly authorize which realms and/or principals can obtain cross-realm TGT to access services across realm boundaries, enhancing security in multi-realm Kerberos deployments.

Changes
New plugin: xrealmauthz

  • Implements the kdcpolicy plugin interface to intercept and authorize cross-realm TGS requests
  • Provides two levels of authorization:
    • Support for both direct and transitive trust
      • Realm-based: Authorize all principals from a specific realm
      • Principal-based: Authorize specific principals
  • Configurable enforcing/monitoring modes via xrealmauthz_enforcing kdc.conf setting
  • Optional pre-approved client realms list via xrealmauthz_allowed_realms kdc.conf setting

Key features:

  1. Granular authorization: Uses principal attributes on cross-realm TGTs to control access
    • xr:@realm - Authorizes all principals from REALM
    • xr:principal - Authorizes specific principal (bare name for direct trust, fully qualified for transitive)
  2. Database-agnostic implementation: Authorization rules are stored as string attributes on cross-realm TGT principals using the standard KDC database API (krb5_dbe_*_string), making the plugin compatible with any KDC database backend (DB2, LMDB, LDAP, etc.)
  3. Monitoring mode: When xrealmauthz_enforcing=false, the plugin logs what it would deny without actually blocking access, useful for testing and migration
  4. Pre-approved realms: Administrators can configure trusted client realms that bypass database authorization checks entirely
  5. Transitive trust support: Correctly handles authorization across multiple realm hops

Testing

  • Comprehensive test suite (t_xrealmauthz.py) covering:
    • Direct realm trust scenarios
    • Transitive trust with proper capaths configuration
    • Principal-specific authorization
    • Enforcing vs monitoring modes
    • Pre-approved realms functionality
  • Tests use the k5test framework for proper realm setup and teardown

Configuration example

[kdcdefaults]
xrealmauthz_enforcing = false # defaults to true
xrealmauthz_allowed_realms = TRUSTED.COM PARTNER.COM # optional
 
[plugins]
kdcpolicy = {
	module = xrealmauthz:/path/to/xrealmauthz.so
}

Authorization examples

realm based:

# Allow all principals from REALM2 to access REALM1
kadmin.local -r REALM1 setstr krbtgt/REALM1@REALM2 xr:@REALM2 ""
# Allow all principals from transitively trusted REALM3 (via REALM2) to access REALM1
kadmin.local -r REALM1 setstr krbtgt/REALM1@REALM2 xr:@REALM3 ""
# Allow all principals from client realm REALM4 to access REALM1 irrespective of the trust path
[kdcdefaults]
xrealmauthz_allowed_realms = REALM4

principal based:

# Allow specific principal from REALM5 (via REALM2) to access REALM1
kadmin.local -r REALM1 setstr krbtgt/REALM1@REALM2 xr:jkelson@REALM5 ""

# Allow specific principal from REALM6 to access REALM1
kadmin.local -r REALM1 setstr krbtgt/REALM1@REALM6 xr:dkelson@REALM6 ""

Compatibility

  • Uses standard k5test framework
  • Compatible with existing cross-realm trust configurations
  • No changes to core KDC behavior when plugin is not loaded

Security considerations

  • Default enforcing mode ensures fail-secure behavior
  • No authorization data exposure to clients
  • Proper validation of all realm and principal names
  • Clear audit trail through KDC logging

This plugin addresses the need for explicit authorization in cross-realm scenarios, providing administrators with fine-grained control over which external principals can access their realm's services.

@greghudson
Copy link
Member

Thanks for submitting this. Before I get into any details, I want to have a conversation to determine whether this functionality has enough general applicability to warrant maintaining it within the upstream krb5 tree.

As a general rule the KDC is responsible primarily for authentication, secondarily for authorization, and usually not for access control. There are some exceptions, such as the require_auth string attribute and the restrict_anonymous_to_tgt variable, but for the most part we make application servers bear the burden of access control decisions.

@abbra has argued that Windows cross-forest trust is not transitive, while cross-realm relationships in MIT krb5 are. However, MIT krb5 implements transited rules that should generally prohibit access from unknown realms. If there are cross-realm keys A<->B and B<->C but A has never heard of C, principals in C should not be able to authenticate to services in A unless A is configured to believe that B is allowed to transit tickets from C.

@dkelson
Copy link
Author
dkelson commented May 14, 2025

You're welcome, and thank you @greghudson for your consideration. I just pushed a 3 line change for the make install that was failing (we currently build the plugin out-of-tree).

I understand the architectural concerns about the KDC's role in access control. This is why we implemented this as an optional kdcpolicy plugin rather than modifying core KDC behavior making use of the interface that enables this exact sort of functionality.

While krb5's transited rules should prohibit access from unknown realms, they don't provide principal-level granularity within trusted known realms or transited realms. As you noted, krb5 traditionally delegates access control decisions to application servers.

To draw an analogy from network security: when protecting thousands of servers, it's common practice to implement firewall rules centrally at the network ingress rather than configuring rules on each individual server. Some organizations prefer defense-in-depth with rules at both layers.

This plugin provides krb5 administrators with a similar architectural choice for cross-realm authorization. Sites can now implement principal-level and realm-level access controls centrally at the KDC level, either as their primary control point or as an additional layer complementing application-level controls. This flexibility allows organizations to choose the security architecture that best fits their needs...whether that's centralized control, distributed control, or defense-in-depth with both.

@abbra
Copy link
Contributor
abbra commented May 15, 2025

I think it makes sense to have per-principal (even with a wildcard) access control at the KDC side because it certainly reduces load on the application servers in making the organisation-wide access control decisions. It could be compared with OAuth2-based IdP situations where it is increasingly common.

@abbra
Copy link
Contributor
abbra commented May 27, 2025

@greghudson as per our offline discussion, I think this kind of plugin would be a special case of the following's requirement in the Active Directory's MS-KILE 3.3.5.7 TGS Exchange section:

If the OTHER_ORGANIZATION SID ([MS-DTYP] section 2.4.2.4) is in KERB_VALIDATION_INFO.ExtraSids, the PAC MUST be used to perform an access check for the Allowed-To-Authenticate right ([MS-ADTS] section 6.1.1.2.7.41) against the Active Directory object of the account for which the service ticket request is being made. If the access check succeeds, the service ticket MUST be issued; otherwise, the KDC MUST return KDC_ERR_POLICY.

This is effectively a trigger during TGS-REQ that requires to apply additional external access check before issuing a ticket. E.g. a principal needs to have the allowed to authenticate right on the target principal to get the ticket issued. And this can be used to limit access to krbtgt/TRUSTED@TRUSTING by defining such rules on the TDOs.

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/2e5dcf34-4b51-44a0-b45a-277ed616ca39

@greghudson
Copy link
Member

To provide some context for Dax: one of the objections I've seen raised to adding KDC access control facilities is that it could encourage application servers to rely on the KDC instead of doing its own access control, in turn creating an interoperability issue (that some application servers could be deployed securely with one KDC implementation but not another). This objection is less compelling if another of the three most-used KDC implementations has already added a similar access control facility.

It looks like there is fairly strong parity between the proposed functionality in this PR and the existing Allowed-to-Authenticate access check in Active Directory. Like the facility in this PR, it appears the Allowed-to-Authenticate check only applies to TGS requests which cross a specifically configured cross-organization trust (those configured with "selective authentication").

So I think that takes care of the general concern. I will review the specifics of the PR and have more feedback soon.

@greghudson
Copy link
Member

Starting with a small but important detail: is "Copyright (C) 2017 by Red Hat, Inc." correct for xrealmauthz/main.c?

I can see two directions for accepting this contribution:

  1. We could treat it like the simple audit plugin module: include it in the source tree mostly unchanged, and run its tests to keep it from bitrotting, but don't install it or mention it in the documentation. This avenue is low-cost but probably carries a narrow benefit. We might find that other people make use of it in spite of the barriers (like we've seen for the "t_s4u" test program), though that would come along with cementing people's reliance on the specific attribute structure.

  2. We could implement a version of this functionality in the KDC core, also taking a hard look at the comparable Active Directory functionality. A lot of details would have to be up for modification in that scenario. AD checks against an ACL list on the service being authenticated to rather than on the trust, which is more flexible but potentially more burdensome. We probably wouldn't want a monitoring mode (there is nothing comparable to that for other policy controls), and we would probably not want the fail-closed behavior. The result might be something that isn't the best fit for the submitter.

@dkelson
Copy link
Author
dkelson commented Jun 2, 2025

Starting with a small but important detail: is "Copyright (C) 2017 by Red Hat, Inc." correct for xrealmauthz/main.c?

It is correct other than the year. I can update that.

I can see two directions for accepting this contribution:

I would advocate for option 1. You are right, the tests are comprehensive and keep it from bitrotting. As an optionally loaded plugin it doesn't impact those who don't need it, and it is currently in use as in at a production site handling 9 million requests a day. The current attribute structure evolved through real-world deployment experience. We encountered and addressed several corner cases in production that shaped the current implementation so its design is battle tested.

@jrisc
Copy link
Contributor
jrisc commented Jun 3, 2025

AD checks against an ACL list on the service being authenticated to rather than on the trust, which is more flexible but potentially more burdensome.

This was the initial idea for this plugin. It had the advantage of optimization, because the server principal entry is passed to the kdcpolicy interface, while storing the ACL in the cross-realm TGT entry requires an extra query. However, it makes it difficult for administrators to fetch the full ACL from kadmin, and some DB backends probably don't have a rich enough query language to fetch it either.

Regarding the direction to take, maybe option 2 is too much of an effort, given the limited audience of this feature, at least for now. Overtime, we could accumulate feedback from our use of this plugin in FreeIPA, and from other users that might be interested in cross-realm access control. Then we could eventually come up with a more final design to be implemented in the core KDC.

In this approach, the priority would be to agree on a stable attribute structure, so ideally it would be possible to switch from the plugin to the future core KDC implementation transparently.

If we keep the plugin, an immediate improvement that could be made in the core KDC however, would be to add a plugin interface to test access rules before processing the TGS-REQ, contrary to the current kdcpolicy one which is called after the ticket is generated.

@greghudson
Copy link
Member

an immediate improvement that could be made in the core KDC however, would be to add a plugin interface to test access rules before processing the TGS-REQ, contrary to the current kdcpolicy one which is called after the ticket is generated.

I don't think that is accurate. The current structure of TGS processing is:

  1. gather_tgs_req_info() to decrypt encrypted parts of the request and look up information
  2. check_tgs_req() to check constraints and policies
  3. tgs_issue_ticket() to construct and encode the ticket (including authdata processing)

kdcpolicy module checks are done in step 2 (check_tgs_req() -> check_kdcpolicy_tgs()). The KDB is also consulted in step 2 (check_tgs_req() -> check_tgs_policy() -> krb5_db_check_policy_tgs()).

@greghudson
Copy link
Member

I've pushed some changes:

  • Status values are generally short all-caps string literals, for easy searchability in the log and the code. Switched to using k5_setmsg() to put the composed message in the error log on denials.
  • Added two more attribute-checking helpers to make check_cross_realm_auth() more readable.
  • Deduplicated code to free the moddata structure.
  • Changed a few names for brevity.
  • Defining _GNU_SOURCE in the makefile is not necessary. We do that globally (since commit 7ecf856).
  • Python test scripts in our suite are supposed to be silent when not run with -v. Changed print() invocations to output() for now.
  • Python test scripts in our suite do not use the main() convention, or try/except Exception wrappers.
  • Removed unnecessary logic in the test script to find the plugin filename and to set up and clean up directories.
  • Fixed some whitespace issues relative to our coding style.

Still to address:

  • The header ticket isn't necessarily a TGT. When it is not, the KDC request must be a forward, proxy, renew, or validate request, and the header ticket server must be the same as the request server. I think xrealmauthz_check() may need additional logic to handle the non-TGT header ticket case; currently I think we would deny renewals of cross-realm service tickets when the client realm isn't pre-approved.
  • Localization _() around some messages.
  • There are some overly defensive checks for receiving null moddata in the check method.
  • The test script uses f-strings, which require Python 3.6, but the test suite only requires Python 3.4.
  • The test script should probably use mark() more and output() less.
  • There may be ways to simplify how the test script sets the enforcing flag.
  • The test script contains long lines (>79 characters).

This code and its tests could get significantly simpler if we ditched the enforcing flag. But I'm willing to leave it in if it's considered important, under the assumption that this remains a plugin module.

@dkelson
Copy link
Author
dkelson commented Jun 5, 2025

I'll review the specific changes, but the list looks very reasonable.

This code and its tests could get significantly simpler if we ditched the enforcing flag. But I'm willing to leave it in if it's considered important, under the assumption that this remains a plugin module.

Regarding the enforcing flag: I strongly believe it's worth keeping. The first version of the plugin did not have this feature. When we first deployed this plugin in a complex production environment with both krb5 and AD realms, we encountered many unanticipated denials. For example, AD clients with direct trust to a krb5 realm were presenting cross-realm TGTs from other trusted realms instead of using the direct path.

I then added the enforcing flag to allow us to run in monitoring mode and analyze logs for a month to identify these misconfigurations and edge cases. This approach mirrors SELinux's enforcing vs permissive modes and other authorization software. Without this capability, we would have had broken production workflows.

Based on this experience, I consider the enforcing flag an important feature that significantly reduces deployment risk, especially in complex environments where the plugin is most likely to be deployed.

@greghudson greghudson force-pushed the master branch 3 times, most recently from 0f46b48 to fbe475b Compare June 9, 2025 18:39
@dkelson
Copy link
Author
dkelson commented Jun 9, 2025

@greghudson taking a look at the changes you made and everything looks great to me. One question though, since it requires manual configuration steps to load, why not have it be installed by the build system? End users that rely on their linux distribution krb5 packages will need to build their own or open tickets with their distro to modify their distro's packages to include the module.

@greghudson
Copy link
Member

This was previously discussed in the "two directions" comment I made earlier. If we install an artifact from the build system, it is incumbent upon the project to document it and support it, whether it's activated by default or not. If the project is going to do that for this functionality, the shape of the design (almost certainly including the fail-closed behavior and monitoring flag) would likely need to change to better align with MIT krb5's existing policy controls.

If the project merely includes the source code in the tree and run its tests to make sure it keeps working in the face of changes elsewhere, that alignment is much less important.

It is true that distributions will have to take an active step to facilitate the use of this module. That is intentional, as they will be taking on the burden of documenting and supporting it for their users.

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]
@greghudson greghudson merged commit ae8801b into krb5:master Jun 16, 2025
6 checks passed
@dkelson
Copy link
Author
dkelson commented Jun 16, 2025

@greghudson thanks for merging the code into master! When you modify the README my full name is:

Dax Kelson

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0