@@ -604,18 +604,36 @@ pub struct JrecTokenClaims {
604604
605605#[ derive( Clone ) ]
606606pub struct KdcTokenClaims {
607- /// Kerberos realm.
608- ///
609- /// E.g.: `ad.it-help.ninja`
610- /// Should be lowercased (actual validation is case-insensitive though).
611- pub krb_realm : SmolStr ,
607+ /// Where the KDC traffic for this session is routed.
608+ pub destination : KdcDestination ,
609+ }
612610
613- /// Kerberos KDC address.
611+ /// Destination for a KDC session token.
612+ ///
613+ /// Either a real upstream KDC (the typical case, with `krb_realm` + `krb_kdc` on the wire) or
614+ /// a credential-injection placeholder pointing at the JTI of the access token whose credentials
615+ /// must be used to forge Kerberos tickets locally (the `jet_cred_id` claim on the wire).
616+ #[ derive( Clone ) ]
617+ pub enum KdcDestination {
618+ /// Forward to an upstream KDC.
619+ Real {
620+ /// Kerberos realm.
621+ ///
622+ /// E.g.: `ad.it-help.ninja`
623+ /// Should be lowercased (actual validation is case-insensitive though).
624+ krb_realm : SmolStr ,
625+
626+ /// Kerberos KDC address.
627+ ///
628+ /// E.g.: `tcp://IT-HELP-DC.ad.it-help.ninja:88`
629+ /// Default scheme is `tcp`.
630+ /// Default port is `88`.
631+ krb_kdc : TargetAddr ,
632+ } ,
633+ /// Serve the request locally using credentials injected at session establishment.
614634 ///
615- /// E.g.: `tcp://IT-HELP-DC.ad.it-help.ninja:88`
616- /// Default scheme is `tcp`.
617- /// Default port is `88`.
618- pub krb_kdc : TargetAddr ,
635+ /// `jti` is the JTI of the access token that provisioned the credentials to use.
636+ Inject { jti : Uuid } ,
619637}
620638
621639// ----- jrl claims ----- //
@@ -1389,8 +1407,12 @@ mod serde_impl {
13891407
13901408 #[ derive( Serialize , Deserialize ) ]
13911409 struct KdcClaimsHelper {
1392- krb_realm : SmolStr ,
1393- krb_kdc : SmolStr ,
1410+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1411+ krb_realm : Option < SmolStr > ,
1412+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1413+ krb_kdc : Option < SmolStr > ,
1414+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1415+ jet_cred_id : Option < Uuid > ,
13941416 }
13951417
13961418 impl ser:: Serialize for SessionTtl {
@@ -1637,11 +1659,20 @@ mod serde_impl {
16371659 where
16381660 S : serde:: Serializer ,
16391661 {
1640- KdcClaimsHelper {
1641- krb_realm : self . krb_realm . clone ( ) ,
1642- krb_kdc : SmolStr :: new ( self . krb_kdc . as_str ( ) ) ,
1643- }
1644- . serialize ( serializer)
1662+ let helper = match & self . destination {
1663+ KdcDestination :: Real { krb_realm, krb_kdc } => KdcClaimsHelper {
1664+ krb_realm : Some ( krb_realm. clone ( ) ) ,
1665+ krb_kdc : Some ( SmolStr :: new ( krb_kdc. as_str ( ) ) ) ,
1666+ jet_cred_id : None ,
1667+ } ,
1668+ KdcDestination :: Inject { jti } => KdcClaimsHelper {
1669+ krb_realm : None ,
1670+ krb_kdc : None ,
1671+ jet_cred_id : Some ( * jti) ,
1672+ } ,
1673+ } ;
1674+
1675+ helper. serialize ( serializer)
16451676 }
16461677 }
16471678
@@ -1654,28 +1685,42 @@ mod serde_impl {
16541685
16551686 let claims = KdcClaimsHelper :: deserialize ( deserializer) ?;
16561687
1657- // Validate krb_realm value
1688+ let destination = match ( claims. krb_realm , claims. krb_kdc , claims. jet_cred_id ) {
1689+ ( Some ( krb_realm) , Some ( krb_kdc) , None ) => {
1690+ // Validate krb_realm value
16581691
1659- if claims. krb_realm . chars ( ) . any ( char:: is_uppercase) {
1660- return Err ( de:: Error :: custom ( "krb_realm field contains uppercases" ) ) ;
1661- }
1692+ if krb_realm. chars ( ) . any ( char:: is_uppercase) {
1693+ return Err ( de:: Error :: custom ( "krb_realm field contains uppercases" ) ) ;
1694+ }
1695+
1696+ // Validate krb_kdc field
16621697
1663- // Validate krb_kdc field
1698+ let krb_kdc = TargetAddr :: parse ( & krb_kdc, DEFAULT_KDC_PORT ) . map_err ( de:: Error :: custom) ?;
1699+ match krb_kdc. scheme ( ) {
1700+ "tcp" | "udp" => { /* supported! */ }
1701+ unsupported_scheme => {
1702+ return Err ( de:: Error :: custom ( format ! (
1703+ "unsupported protocol for KDC proxy: {unsupported_scheme}"
1704+ ) ) ) ;
1705+ }
1706+ }
16641707
1665- let krb_kdc = TargetAddr :: parse ( & claims. krb_kdc , DEFAULT_KDC_PORT ) . map_err ( de:: Error :: custom) ?;
1666- match krb_kdc. scheme ( ) {
1667- "tcp" | "udp" => { /* supported! */ }
1668- unsupported_scheme => {
1669- return Err ( de:: Error :: custom ( format ! (
1670- "unsupported protocol for KDC proxy: {unsupported_scheme}"
1671- ) ) ) ;
1708+ KdcDestination :: Real { krb_realm, krb_kdc }
16721709 }
1673- }
1710+ ( None , None , Some ( jti) ) => KdcDestination :: Inject { jti } ,
1711+ ( None , None , None ) => {
1712+ return Err ( de:: Error :: custom (
1713+ "missing KDC destination: expected `krb_realm`+`krb_kdc` or `jet_cred_id`" ,
1714+ ) ) ;
1715+ }
1716+ _ => {
1717+ return Err ( de:: Error :: custom (
1718+ "conflicting KDC destination fields: `jet_cred_id` is mutually exclusive with `krb_realm`+`krb_kdc`" ,
1719+ ) ) ;
1720+ }
1721+ } ;
16741722
1675- Ok ( Self {
1676- krb_realm : claims. krb_realm ,
1677- krb_kdc,
1678- } )
1723+ Ok ( Self { destination } )
16791724 }
16801725 }
16811726}
0 commit comments