[ 
https://issues.apache.org/jira/browse/KNOX-3403?focusedWorklogId=1033139&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1033139
 ]

ASF GitHub Bot logged work on KNOX-3403:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 31/Jul/26 05:11
            Start Date: 31/Jul/26 05:11
    Worklog Time Spent: 10m 
      Work Description: smolnar82 merged PR #1336:
URL: https://github.com/apache/knox/pull/1336




Issue Time Tracking
-------------------

    Worklog Id:     (was: 1033139)
    Time Spent: 0.5h  (was: 20m)

> RFC 8693 token exchange: issued token 'sub' is the actor instead of the 
> subject on non-server-managed topologies
> ----------------------------------------------------------------------------------------------------------------
>
>                 Key: KNOX-3403
>                 URL: https://issues.apache.org/jira/browse/KNOX-3403
>             Project: Apache Knox
>          Issue Type: Bug
>          Components: Server
>    Affects Versions: 3.0.0
>            Reporter: Sandor Molnar
>            Assignee: Sandor Molnar
>            Priority: Critical
>             Fix For: 3.0.0
>
>          Time Spent: 0.5h
>  Remaining Estimate: 0h
>
> h2. Summary
> When a token is issued via an RFC 8693 token exchange 
> ({{{}grant_type=urn:ietf:params:oauth:grant-type:token-exchange{}}}, with a 
> {{subject_token}} and an {{{}actor_token{}}}) on a topology whose 
> {{KNOXTOKEN}} service is *not* server-managed, the issued JWT's {{sub}} is 
> set to the *actor* (the authenticated caller / primary principal) instead of 
> the *subject* (the impersonated end user). The impersonated identity is 
> effectively dropped from {{{}sub{}}}, defeating the on-behalf-of semantics.
> h2. Impact
>  * RFC 8693 OBO on a non-server-managed topology: the exchanged token 
> represents the acting party (e.g. the service account) rather than the end 
> user. The {{act}} claim is correct; only {{sub}} is wrong.
>  * Traditional {{doAs}} is NOT affected - there the identity-assertion 
> request wrapper already exposes the impersonated user via 
> {{{}getUserPrincipal(){}}}, so the issued {{sub}} is correct.
>  * Server-managed topologies are NOT affected - the impersonation-aware 
> username resolution already runs.
> h2. Reproduction
>  # Deploy a NON-server-managed topology with a JWTProvider (trusting both the 
> subject and actor issuers) + KNOXTOKEN 
> ({{{}knox.token.enable.delegated.auth=true{}}}) + identity-assertion Default.
>  # Obtain a {{subject_token}} (an end user, e.g. admin) and an 
> {{actor_token}} for a DIFFERENT principal (e.g. a k8s service account).
>  # POST the exchange:
> {code:java}
>   grant_type=urn:ietf:params:oauth:grant-type:token-exchange
>   subject_token=<user JWT>
>   subject_token_type=urn:ietf:params:oauth:token-type:jwt
>   actor_token=<service-account JWT>
>   actor_token_type=urn:ietf:params:oauth:token-type:jwt
>   {code}
>  # Decode the issued access_token:
>  ** Actual: {{sub}} = the actor (e.g. system:serviceaccount:...), {{act}} = 
> \{ sub: actor }
>  ** Expected: {{sub}} = the subject (the end user), {{act}} = \{ sub: actor }
>  # Running the same exchange on a server-managed topology yields the correct 
> {{sub}} - isolating the cause to the non-server-managed code path.
> h2. Root cause
> {{TokenResource.buildUserContext(HttpServletRequest)}} initializes the token 
> username from {{request.getUserPrincipal().getName()}} and only replaces it 
> with the impersonated principal inside an {{if (tokenStateService != null)}} 
> (i.e. server-managed) block:
> {code:java}
> String userName = request.getUserPrincipal().getName();
> String createdBy = null;
> if (tokenStateService != null) {                       // <-- only when 
> server-managed
>   final Subject subject = SubjectUtils.getCurrentSubject();
>   if (subject != null && SubjectUtils.isImpersonating(subject)) {
>     String primaryPrincipalName      = 
> SubjectUtils.getPrimaryPrincipalName(subject);
>     String impersonatedPrincipalName = 
> SubjectUtils.getImpersonatedPrincipalName(subject);
>     if (!primaryPrincipalName.equals(impersonatedPrincipalName)) {
>       createdBy = primaryPrincipalName;
>       userName  = impersonatedPrincipalName;
>     }
>   }
> }
> {code}
> In the RFC 8693 token-exchange flow the principal reaching TokenResource via 
> {{getUserPrincipal()}} is the actor (primary principal), so the impersonated 
> subject is only applied by the override above. Because that override is gated 
> on {{{}tokenStateService != null{}}}, it is skipped on non-server-managed 
> topologies and {{sub}} falls back to the actor.
> The gate predates token exchange and was intended only to guard metadata 
> storage ({{{}userName{}}}/{{{}createdBy{}}} persisted to the token state 
> service), not to control the issued token's {{{}sub{}}}.
> h2. Fix
> Move the impersonation -> {{userName}} resolution out of the 
> {{tokenStateService != null}} block so the issued token's {{sub}} is the 
> effective (impersonated) identity whenever 
> {{SubjectUtils.isImpersonating(subject)}} is true, regardless of 
> server-managed state. {{createdBy}} continues to be persisted only when 
> server-managed ({{{}persistTokenDetails{}}} already guards on 
> {{{}tokenStateService != null{}}}, so computing it otherwise is harmless). 
> Remove the now-stale comment.
> h2. Tests
> Add coverage in {{TokenServiceResourceTest}} on a NON-server-managed topology:
>  * An RFC 8693 / impersonated subject (PrimaryPrincipal = actor, 
> ImpersonatedPrincipal = subject, getUserPrincipal() = actor) yields \{sub == 
> subject} and \{act == { sub: actor }}.
>  * A non-impersonating request still yields \{sub == primary}(no regression).
>  * Confirm server-managed behavior is unchanged.
> h2. Related
> Discovered while validating KNOX-3399 (RFC 8693 token exchange) on a 
> non-server managed exchange topology. Independent of that routing fix.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to