This is an automated email from the ASF dual-hosted git repository.
smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new da08af1f0 KNOX-3403: RFC 8693 token exchange: use the impersonated
subject as the token sub on non-server-managed topologies (#1336)
da08af1f0 is described below
commit da08af1f0f44a437d3e9cf3e019215013e2f1df0
Author: Sandor Molnar <[email protected]>
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);
+ }
}