Sandor Molnar created KNOX-3403:
-----------------------------------

             Summary: 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
             Fix For: 3.0.0


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