Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -920,18 +920,19 @@ protected Response onlyAllowGroupsToBeAddedWhenEnabled() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2220,4 +2220,55 @@ public void testNoActClaimWithoutImpersonation() throws Exception {

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);
}
}
Loading