[
https://issues.apache.org/jira/browse/KNOX-3403?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Sandor Molnar updated KNOX-3403:
--------------------------------
Resolution: Fixed
Status: Resolved (was: Patch Available)
> 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)