A MIT KDC policy plugin for cross-realm authorization

4onthefloor1241 pts0 comments

A KDC policy plugin for cross-realm authorization in MIT Kerberos — Guru Labs Blog

xrealmauthz, a KDC policy plugin upstreamed into MIT Kerberos, authorizes cross-realm clients per realm or per principal at ticket issuance.

~/blog $<br>cat kerberos-cross-realm-authorization-xrealmauthz.md<br>Dax Kelson<br>·June 28, 2026<br>6 min read

Last year I helped a Fortune 50 client modernize the largest MIT Kerberos deployment I've worked on: tens of thousands of Windows, Linux, and UNIX hosts across several realms. Their security policy required authorization checks at the KDC itself, which they had built by patching the MIT KDC source and running a modified KDC in-house. That patch pinned them to an old MIT Kerberos release and made version upgrades and security fixes hard to apply. I wrote the authorization logic as a kdcpolicy plugin, xrealmauthz, so they could run stock MIT Kerberos and retire the in-house fork, and submitted it upstream; it merged in June 2025 (krb5 PR #1431).

Kerberos authenticates but does not authorize: the KDC proves who you are and issues tickets, and each service decides what you may do with one. At the ticket level, trusting another realm trusts every principal in it, and every principal in the realms it transitively trusts. With xrealmauthz, a stock MIT Kerberos KDC can authorize cross-realm clients per realm or per principal at the moment it issues the ticket. For the first time, the KDC itself makes that decision about principals from other realms, instead of leaving it to each service.

How cross-realm trust works

A cross-realm trust is a shared key, stored as a krbtgt/REALM1@REALM2 principal in both databases. Once it exists, any principal that authenticated to REALM2 can present its TGT to REALM1's KDC and receive a REALM1 service ticket. [capaths] constrains the transit path and permits transitive trust (REALM3 via REALM2), but nothing constrains which foreign principal is asking.

The kdcpolicy hook

The kdcpolicy plugin interface (krb5 1.16) exposes two entry points, check_as and check_tgs, that the KDC calls before issuing a ticket. A module returns success to allow, KRB5KDC_ERR_POLICY to deny, and may shorten ticket lifetimes. Until this plugin the only in-tree module was a test stub; nothing shipped used the hook for a real decision.

Cross-realm requests always arrive as a TGS-REQ carrying a cross-realm TGT, so xrealmauthz implements check_tgs only. It runs after the KDC has validated the cross-realm TGT and the transited path, as the last gate before issuance:

%%{init: {'theme':'base','fontFamily':'Open Sans, sans-serif','themeVariables':{'fontFamily':'Open Sans, sans-serif','fontSize':'16px','lineColor':'#00609A','edgeLabelBackground':'#FFFFFF','background':'#FFFFFF','clusterBkg':'#F5F9FD','clusterBorder':'#C5D7E8','titleColor':'#003C60'},'flowchart':{'htmlLabels':true,'useMaxWidth':true,'nodeSpacing':48,'rankSpacing':52,'curve':'basis'}}}%%<br>flowchart LR<br>subgraph B["Existing KDC cross-realm flow"]<br>direction TB<br>bC["Client (REALM2)"] --> bK["KDC (REALM1)"]<br>bK --> bT["KDC issues service ticket"]<br>bT --> bS["Service (REALM1)"]<br>end<br>subgraph A["KDC using my xrealmauthz plugin"]<br>direction TB<br>aC["Client (REALM2)"] --> aK["KDC (REALM1)"]<br>aK -->|"check_tgs"| aX["xrealmauthz1. client realm pre-approved?2. xr:@CLIENTREALM on krbtgt entry?3. xr:CLIENTPRINC on krbtgt entry?"]<br>aX -->|"a rule matches"| aT["KDC issues service ticket"]<br>aX -->|"no rule matches"| aD["Policy errorKRB5KDC_ERR_POLICY"]<br>aT --> aS["Service (REALM1)"]<br>end

bC ~~~ aC

classDef infra fill:#EAF2FB,stroke:#0078C1,stroke-width:2px,color:#003C60<br>classDef plugin fill:#FBE3B3,stroke:#ED8C0C,stroke-width:2.5px,color:#5A3A00<br>classDef deny fill:#FBE0E0,stroke:#C0392B,stroke-width:1.6px,color:#7A1C1C<br>class bC,bK,bT,bS,aC,aK,aT,aS infra<br>class aX plugin<br>class aD deny<br>linkStyle default stroke:#00609A,color:#1F2937

Cross-realm TGS request flow, without and with xrealmauthz. In the existing flow (left), REALM1's KDC issues a service ticket to any cross-realm client. With the plugin (right), the amber check_tgs gate runs its rule checks and either issues the ticket or returns a policy error.

Authorization rules live on the krbtgt entry

The rules are string attributes on the krbtgt/MYREALM@OTHERREALM entry, read with krb5_dbe_get_string (set with kadmin setstr) and namespaced with an xr: prefix. This needs no new config file and no schema change: every KDB backend (DB2, LMDB, LDAP) supports string attributes, and the rules replicate with the database. They also attach to the exact trust edge a request crosses, which is what makes transitive authorization work.

Enable the module in kdc.conf:

[plugins]<br>kdcpolicy = {<br>module = xrealmauthz:/usr/lib/krb5/plugins/kdcpolicy/xrealmauthz.so

Realm and principal rules

Two rule forms live under xr:. xr:@CLIENTREALM authorizes every principal from a realm; xr:PRINC authorizes one client. For a principal rule, omit the realm when the client is in the krbtgt's far-end realm, and include it...

realm cross plugin principal ticket xrealmauthz

Related Articles