Knox 3334 - Introduce ActorChainPrincipal for RFC 8693 instead of ImpersonatedPrincipal#1257
Knox 3334 - Introduce ActorChainPrincipal for RFC 8693 instead of ImpersonatedPrincipal#1257lmccay wants to merge 2 commits into
Conversation
Test Results22 tests 22 ✅ 2s ⏱️ Results for commit 8d2a990. |
| public String getName() { | ||
| // Return the current actor's identity as the principal name | ||
| String currentActor = getCurrentActor(); | ||
| return currentActor != null ? currentActor : ""; | ||
| } |
There was a problem hiding this comment.
I did not find any use of this method, I think it can be simply removed.
There was a problem hiding this comment.
I believe getName() is part of the Principal interface and cannot be removed.
| * <p>This is equivalent to calling {@code getActorChain().get(0).get("sub")} | ||
| * if the chain is not empty.</p> | ||
| * | ||
| * @return the subject of the most recent actor, or null if the chain is empty |
There was a problem hiding this comment.
Maybe return Optional<String> instead of null?
| if (enableDelegatedAuth) { | ||
| // First check if there's an existing actor chain from a previous token exchange | ||
| Set<ActorChainPrincipal> actorChainPrincipals = subject.getPrincipals(ActorChainPrincipal.class); | ||
| List<Map<String, Object>> existingChain = null; | ||
| if (!actorChainPrincipals.isEmpty()) { | ||
| existingChain = actorChainPrincipals.iterator().next().getActorChain(); | ||
| log.generalInfoMessage("Found existing actor chain with " + existingChain.size() + " actors"); | ||
| } | ||
|
|
||
| // Check if impersonation is occurring to add a new actor to the chain | ||
| if (SubjectUtils.isImpersonating(subject)) { | ||
| String primaryPrincipalName = SubjectUtils.getPrimaryPrincipalName(subject); | ||
| String impersonatedPrincipalName = SubjectUtils.getImpersonatedPrincipalName(subject); | ||
| if (primaryPrincipalName != null && impersonatedPrincipalName != null && !primaryPrincipalName.equals(impersonatedPrincipalName)) { | ||
| // Build the new actor chain by adding the current actor (primary principal) to the existing chain | ||
| List<Map<String, Object>> newActorChain = TokenUtils.addActorToChain(existingChain, primaryPrincipalName); | ||
| jwtAttributesBuilder.setActorChain(newActorChain); | ||
| log.addingActorClaimToToken(primaryPrincipalName, impersonatedPrincipalName); | ||
| } | ||
| } else if (existingChain != null && !existingChain.isEmpty()) { | ||
| // No new impersonation, but preserve existing actor chain | ||
| jwtAttributesBuilder.setActorChain(existingChain); | ||
| log.generalInfoMessage("Preserving existing actor chain without adding new actor"); |
There was a problem hiding this comment.
This logic deserves its own private method (e.g. handleDelegatedAuth), including the if statement -> here it would only be one method call (e.g. handleDelegatedAuth(...).
|
|
||
| // RFC 8693 Token Exchange: Preserve ActorChainPrincipal from the current subject | ||
| // This ensures the delegation chain is maintained through identity assertion | ||
| final Set<ActorChainPrincipal> actorChainPrincipals = currentSubject.getPrincipals(ActorChainPrincipal.class); |
There was a problem hiding this comment.
We have named methods in SubjectUtils to get different principals from current subject (getCurrentGroupPrincipals, getTokenIdPrincipals, ...), I think we should add getActorChainPrincipals there too.
There was a problem hiding this comment.
Ahh, yes, I forgot to go back and do this but meant to.
| if (token == null) { | ||
| return Collections.emptyList(); | ||
| } | ||
|
|
||
| Object actClaim = token.getClaimAsObject(JWTToken.ACT_CLAIM); | ||
| if (actClaim == null) { | ||
| return Collections.emptyList(); | ||
| } |
There was a problem hiding this comment.
These 2 ifs could be merged:
if (token == null || token.getClaimAsObject(JWTToken.ACT_CLAIM) == null) {
return Collections.emptyList();
}
Maybe something like this?
public static List<Map<String, Object>> extractActorChain(JWT token) {
final Object currentAct = token == null ? null : token.getClaimAsObject(JWTToken.ACT_CLAIM);
final List<Map<String, Object>> actorChain = new ArrayList<>();
while (currentAct instanceof Map<?, ?> actorMap) {
actorChain.add(new LinkedHashMap<>((Map<String, Object>) actorMap));
currentAct = actorMap.get(JWTToken.ACT_CLAIM);
}
return Collections.unmodifiableList(actorChain);
}
(It is very important that you created an Apache Knox JIRA for this change and that the PR title/commit message includes the Apache Knox JIRA ID!)
KNOX-3334 Introduce ActorChainPrincipal for RFC 8693 instead of ImpersonatedPrincipal
What changes were proposed in this pull request?
KNOX-3321 provided an initial implementation for 8693 and adding an 'act' claim to the returned JWT based on the presence of the ImpersonatedPrincipal and having delegated auth enabled on the KnoxToken service.
This falls short of what we need to support token exchanges that already include an 'act' claim in the subject token. To support this properly, we need the previous 'act' claim represented in the Java Subject with the full chain represented. We will then add the next actor subclaim to the chain from within the KnoxToken service, effectively continuing the chain as it flows through the actors for the given request.
To support this, we should introduce a new principal called ActorChainPrincipal which will have an extended interface to provide the list of 'act' claims within the presented token for building out the chain in the new token.
How was this patch tested?
curl -ivku admin:admin-password 'https://localhost:8443/gateway/knoxcreds/knoxtoken/api/v1/token?doAs=guest'
{"access_token":"eyJqa3UiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2dhdGV3YXkva25veGNyZWRzL2tub3h0b2tlbi9hcGkvdjEvandrcy5qc29uIiwia2lkIjoiS1M2enJFSUsxSFJTbUQ4Ri1FckhnOWc2bHlSQ1A1WUcxLUN0TFFaM2U2byIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJndWVzdCIsImFjd...uIiwia2lkIjoiS1M2enJFSUsxSFJTbUQ4Ri1FckhnOWc2bHlSQ1A1WUcxLUN0TFFaM2U2byIsImlzcyI6IktOT1hTU08iLCJtYW5hZ2VkLnRva2VuIjoidHJ1ZSIsImtub3guaWQiOiI1YTk4MzM4ZC1kNjlkLTRlNTEtYWNiMS0yNzVhNzZkYzE4M2UifQ.VJOd0pJFwQpWFWv7Xo661pKFMl8md_1UmYf1kLdQIgeVfLhhwWINAKqpD-9Od6YwWIXlfr3SBGbPoHeMQYZp1fEOt4fx1gyFo08VGhDSJMI63FX93KpvDrdeECKwcKimIhnh9H9VEQLP56WOBqn3eYoc8aJFua4Ydh9dC0b0AbnVSrDqoS3hOJLwGsj602NvsIU5IoxmYz8s7rNO7CN6qynxmTp-w4g1Q3skmqU8zki9DvEMJXMRdMsflssFDWrybw0UvzMjjmKQxzKqcb9jsnM3lrsqit7-JEg6kPNA5M5IWIKgkIgt-P_iYAr6ouXt0BxxGfJplK_rF2qxkD98xg","token_id":"5a98338d-d69d-4e51-acb1-275a76dc183e","managed":"true","endpoint_public_cert":"MIIDZDCCAkygAwIBAgIISWFJksv5UD4wDQYJKoZIhvcNAQELBQAwXzESMBAGA1UEAwwJbG9jYWxob3N0MQ0wCwYDVQQLDARUZXN0MQ8wDQYDVQQKDAZIYWRvb3AxDTALBgNVBAcMBFRlc3QxDTALBgNVBAgMBFRlc3QxCzAJBgNVBAYTAlVTMB4XDTI2MDYxMDIxMTg1M1oXDTI3MDYxMDIxMTg1M1owXzESMBAGA1UEAwwJbG9jYWxob3N0MQ0wCwYDVQQLDARUZXN0MQ8wDQYDVQQKDAZIYWRvb3AxDTALBgNVBAcMBFRlc3QxDTALBgNVBAgMBFRlc3QxCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApSDLZB9y0gV5W9HEmV49GAo4PH9JTmfISUCTGYZ5z6pwU0ierFz0Qm....OCNkLF4fj5iQAqyp+6lF7e1Wj50oaKCTsLkxV+3bn2PVdI7rYR9wS6t6V7MuoaLbgcL/RY8/ec3qUC6NvQIDAQABoyQwIjAgBgNVHREEGTAXggpHV1dZRkc2NU1Rgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAD8URUGKn4Em8AtVBrLxfwxyvPxWHXUONERbFKizIV2xkTcxgNnoKKUaHX2A8f0DW1OQ1KsiR2mp8ygeZcqgsCcEpPGnzYnfknD58nEDOXPpg2vy64ppIXzXPbR6PUBQo9zuIH5n+JlFhR5dP0WugqdNarKwtNCSjDRmjiLki4quuM8Wd4kSflHZmFwehqUH0Hc4mtdvrrCZUrz3RnK0N/oBW3Mbr5/0gj6CzC4YgKqCipRzCvJ9RKrewOumPLhg5m2Zb6NfDJXVFG4WhrYF+dRX0Dljngy2eLsTOdg4nlenRZYZWZL6/UpnpjSik/J3AFB7CPLStC280QP6m/89R5o=","token_type":"Bearer","expires_in":-1,"passcode":"TldFNU9ETXpPR1F0WkRZNVpDMDBaVFV4TFdGallqRXRNamMxWVRjMlpHTXhPRE5sOjpZVEF5TlRVeFlqSXRPR05rTVMwME56STFMVGhrTlRZdE1USmtNamxpT0dSbVpURmg="}%curl -ivk -H 'Authorization: Bearer eyJqa3UiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2dhdGV3YXkva25veGNyZWRzL2tub3h0b2tlbi9hcGkvdjEvandrcy5qc29uIiwia2lkIjoiS1M2enJFSUsxSFJTbUQ4Ri1....1ZSIsImtub3guaWQiOiI1YTk4MzM4ZC1kNjlkLTRlNTEtYWNiMS0yNzVhNzZkYzE4M2UifQ.VJOd0pJFwQpWFWv7Xo661pKFMl8md_1UmYf1kLdQIgeVfLhhwWINAKqpD-9Od6YwWIXlfr3SBGbPoHeMQYZp1fEOt4fx1gyFo08VGhDSJMI63FX93KpvDrdeECKwcKimIhnh9H9VEQLP56WOBqn3eYoc8aJFua4Ydh9dC0b0AbnVSrDqoS3hOJLwGsj602NvsIU5IoxmYz8s7rNO7CN6qynxmTp-w4g1Q3skmqU8zki9DvEMJXMRdMsflssFDWrybw0UvzMjjmKQxzKqcb9jsnM3lrsqit7-JEg6kPNA5M5IWIKgkIgt-P_iYAr6ouXt0BxxGfJplK_rF2qxkD98xg' 'https://localhost:8443/gateway/knoxidf/knoxtoken/v1/oauth/tokens?doAs=tom'
{"access_token":"eyJqa3UiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2dhdGV3YXkva25veGlkZi9rbm94dG9rZW4vdjEvb2F1dGgvandrcy5qc29uIiwia2lkIjoiS1M2enJFSUsxSFJTbUQ4Ri1FckhnOWc2bHlSQ1A1WUcxLUN0TFFaM2U2byIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJ0b20iLCJhY3QiOn....ZTZvIiwiaXNzIjoiS05PWFNTTyIsIm1hbmFnZWQudG9rZW4iOiJ0cnVlIiwia25veC5pZCI6IjQwYTFmNmNmLWRmYzQtNDU3Ny05MmNjLWEwMTI5M2RkYWZiZCJ9.kUi8Mc7E7njrrh3B-UWgDjJVlWm23rRmd6p9en79Q-6Q_I5qw1LQf6bqUPqTwZ35JgZwfcSodFcSuitJwgOOoAbhx3qZR8mTVhoqNyoUtSqmtixkSvMQwXTBZtcWDKyjc208LZCs6Vv21T_oHXHuInSUZnTjghoM_gh1JrcIkxvLPaz8utL-aWc_1cJvNKNvQtz5uuU_FynLLhBCJQ-dJnKOlJ4-HXZwlnA9QhaEQDW-07n-R7ZhM_urqrrmYdYKNlyRztoCiD7M13pSwdjxf4AQura1vV7AyRwEEfI1gTPZ7L2xP52dVBU1R_sBz83NuXzSOumpGWJa8x4eWOIXsA","refresh_token":"d7622c25-4514-463d-a608-37aadbba0cce","issued_token_type":"urn:ietf:params:oauth:token-type:access_token","token_type":"Bearer","expires_in":-1}