You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2026/07/31 06:39:45 UTC

(knox) branch v3.0.0 updated: KNOX-3403: RFC 8693 token exchange: use the impersonated subject as the token sub on non-server-managed topologies (#1336)

This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch v3.0.0
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/v3.0.0 by this push:
     new 65935d219 KNOX-3403: RFC 8693 token exchange: use the impersonated subject as the token sub on non-server-managed topologies (#1336)
65935d219 is described below

commit 65935d2193f6ff5232303c2a1cbb4abcfd0982a6
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Fri Jul 31 07:11:35 2026 +0200

    KNOX-3403: RFC 8693 token exchange: use the impersonated subject as the token sub on non-server-managed topologies (#1336)
---
 .../gateway/service/knoxtoken/TokenResource.java   | 25 ++++++-----
 .../knoxtoken/TokenServiceResourceTest.java        | 51 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 12 deletions(-)

diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 8da6f137b..6d876648b 100644
--- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -920,18 +920,19 @@ public class TokenResource {
   protected UserContext buildUserContext(HttpServletRequest request) {
     String userName = request.getUserPrincipal().getName();
     String createdBy = null;
-    // checking the doAs user only makes sense if tokens are managed (this is where we store the userName/createdBy information)
-    // and if impersonation was enabled before (on HadoopAuth or identity-assertion level) so the the current subject has at least one ImpersonatedPrincipal principal
-    if (tokenStateService != null) {
-      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;
-          log.tokenImpersonationSuccess(createdBy, userName);
-        }
+    // When impersonation is in effect, the issued token's subject must be the effective (impersonated)
+    // identity, not the primary principal. This applies to traditional doAs as well as RFC 8693 token
+    // exchange (where the actor is the primary principal and the subject is the impersonated one).
+    // This is independent of server-managed state: createdBy is only persisted for managed tokens (see
+    // persistTokenDetails), so computing it here is harmless when there is no token state service.
+    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;
+        log.tokenImpersonationSuccess(createdBy, userName);
       }
     }
     return new UserContext(userName, createdBy);
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 281ea90ea..8398e168b 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -2220,4 +2220,55 @@ public class TokenServiceResourceTest {
 
     EasyMock.verify(request, context);
   }
+
+  /**
+   * KNOX-3403: On a NON-server-managed topology, an impersonated request (traditional doAs or RFC 8693
+   * token exchange, where the actor is the primary principal and the subject is the impersonated one)
+   * must still issue a token whose {@code sub} is the impersonated subject. Previously buildUserContext
+   * only applied the impersonated identity when {@code tokenStateService != null}, so on a non-managed
+   * topology the {@code sub} incorrectly fell back to the primary principal (the actor).
+   */
+  @Test
+  @SuppressForbidden
+  public void testImpersonatedTokenSubjectOnNonServerManagedTopology() throws Exception {
+    final String primaryUser = "admin";      // primary principal (authenticated caller / actor)
+    final String impersonatedUser = "bob";  // impersonated subject (distinct from USER_NAME)
+
+    // No serverManagedTssEnabled argument -> token state service is absent (non-server-managed).
+    configureCommonExpectations(createDelegatedAuthContextExpectations(true, true));
+    Subject subject = createSubjectWithOptionalImpersonation(primaryUser, impersonatedUser);
+    JWTToken parsedToken = getTokenWithSubject(subject);
+
+    // The token subject must be the impersonated user, not the primary.
+    assertEquals("Non-server-managed impersonated token must use the impersonated subject as sub",
+            impersonatedUser, parsedToken.getSubject());
+
+    // The 'act' claim still records the primary user (delegated auth enabled).
+    Object actClaim = parsedToken.getClaimAsObject(JWTToken.ACT_CLAIM);
+    assertNotNull("RFC 8693 'act' claim should be present", actClaim);
+    assertTrue("'act' claim should be a Map", actClaim instanceof Map);
+    @SuppressWarnings("unchecked")
+    Map<String, Object> actClaimMap = (Map<String, Object>) actClaim;
+    assertEquals("'act' claim should contain the primary user's subject", primaryUser, actClaimMap.get("sub"));
+
+    EasyMock.verify(request, context);
+  }
+
+  /**
+   * KNOX-3403: sanity check that decoupling the impersonated-sub from server-managed does not change
+   * the non-impersonating case - the token {@code sub} remains the authenticated (primary) user on a
+   * non-server-managed topology.
+   */
+  @Test
+  @SuppressForbidden
+  public void testNonImpersonatedTokenSubjectOnNonServerManagedTopology() throws Exception {
+    configureCommonExpectations(createDelegatedAuthContextExpectations(true, false));
+    Subject subject = createSubjectWithOptionalImpersonation(USER_NAME, null);
+    JWTToken parsedToken = getTokenWithSubject(subject);
+
+    assertEquals(USER_NAME, parsedToken.getSubject());
+    assertNull("'act' claim should NOT be present without impersonation", parsedToken.getClaimAsObject(JWTToken.ACT_CLAIM));
+
+    EasyMock.verify(request, context);
+  }
 }