Skip to content

Add SPIFFE claim support to ZTS ID token exchange#3254

Open
ctyano wants to merge 4 commits intoAthenZ:masterfrom
ctyano:zts-id-token-exchange-jwt-svid-spiffe
Open

Add SPIFFE claim support to ZTS ID token exchange#3254
ctyano wants to merge 4 commits intoAthenZ:masterfrom
ctyano:zts-id-token-exchange-jwt-svid-spiffe

Conversation

@ctyano
Copy link
Copy Markdown
Contributor

@ctyano ctyano commented Mar 22, 2026

Description

#3233

Contribution Checklist:

  • The pull request does not introduce any breaking changes
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Attach Screenshots (Optional)

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the ZTS (ZMS Token Service) ID token exchange functionality by introducing support for SPIFFE (Secure Production Identity Framework For Everyone) claims. It enables the ZTS to propagate SPIFFE IDs from incoming subject tokens to newly issued ID and Access tokens, ensuring that identity context is maintained across token exchanges. Furthermore, it adds a crucial validation step to confirm that the SPIFFE ID in the subject token aligns with the authenticated Athenz principal, thereby strengthening the security posture of the token exchange process.

Highlights

  • SPIFFE ID Propagation: Implemented the copying of SPIFFE IDs from subject tokens to issued ID and Access tokens during token exchange, ensuring identity context is maintained.
  • SPIFFE ID Validation: Introduced a mechanism to verify that the SPIFFE ID in a subject token matches the authenticated Athenz principal during ID token exchange, enhancing security.
  • Documentation Update: Updated the zts_token_exchange_requirements.md document to reflect the new SPIFFE claim handling in ID and Access token exchanges.
  • Test Coverage: Added new unit tests and updated existing ones to validate the correct propagation and validation of SPIFFE claims in exchanged tokens.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for propagating SPIFFE claims during various token exchange flows, which is a valuable enhancement. The implementation correctly adds the SPIFFE claim to new tokens in impersonation, delegation, and ID token exchange flows. It also introduces a security verification for the SPIFFE claim in the ID token exchange flow. Additionally, the PR includes a fix for an authorization check in the access token delegation flow and corrects the authorization logic in the ID token exchange flow. The test coverage for the new functionality is good. I have one critical comment regarding the principal being used for SPIFFE ID verification and a couple of medium-severity suggestions to enhance security by adding similar verification to other flows.

if (spiffeId != null) {
// If we're copying SPIFFE claim into the requested ID token, verify it maps
// to the authenticated principal to prevent mismatched SPIFFE identities.
verifySpiffeIdMatchesAthenzPrincipal(spiffeId, principalName, caller, domainName, principalDomain);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The verifySpiffeIdMatchesAthenzPrincipal method is called with principalName, which is the identity of the caller making the token exchange request. However, the spiffeId is extracted from the subjectToken, and the new ID token is being issued for the subjectIdentity. The verification should be against the subjectIdentity to ensure the SPIFFE ID correctly corresponds to the subject of the token, preventing a caller from injecting a SPIFFE ID that doesn't belong to the token's subject.

The comment on line 3065 mentions "authenticated principal", which is the caller, but for security, the check should be against the identity for which the claim is being made.

Please note that with this change, the test testIdTokenExchangeSuccessWithSpiffeClaim in ZTSImplIDTokenTest.java will likely fail and will need to be updated. The spiffeId used in that test should correspond to the subject of the token being created.

Suggested change
verifySpiffeIdMatchesAthenzPrincipal(spiffeId, principalName, caller, domainName, principalDomain);
verifySpiffeIdMatchesAthenzPrincipal(spiffeId, subjectIdentity, caller, domainName, principalDomain);

Comment on lines +2734 to +2737
final String spiffeId = extractSpiffeIdFromToken(subjectToken);
if (spiffeId != null) {
accessToken.setCustomClaim(IdToken.CLAIM_SPIFFE, spiffeId);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

For consistency and improved security, it would be best to verify that the SPIFFE ID from the subject token corresponds to the subject of the token, similar to the check in processIdTokenExchangeRequest. This prevents the propagation of a potentially mismatched SPIFFE ID if the subject token were ever compromised or misconfigured.

Suggested change
final String spiffeId = extractSpiffeIdFromToken(subjectToken);
if (spiffeId != null) {
accessToken.setCustomClaim(IdToken.CLAIM_SPIFFE, spiffeId);
}
final String spiffeId = extractSpiffeIdFromToken(subjectToken);
if (spiffeId != null) {
verifySpiffeIdMatchesAthenzPrincipal(spiffeId, subjectToken.getSubject(), caller, requestDomainName, principalDomain);
accessToken.setCustomClaim(IdToken.CLAIM_SPIFFE, spiffeId);
}

Comment on lines +2900 to +2903
final String spiffeId = extractSpiffeIdFromToken(subjectToken);
if (spiffeId != null) {
accessToken.setCustomClaim(IdToken.CLAIM_SPIFFE, spiffeId);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

For consistency and improved security, it would be best to verify that the SPIFFE ID from the subject token corresponds to the subject of the token, similar to the check in processIdTokenExchangeRequest. This prevents the propagation of a potentially mismatched SPIFFE ID if the subject token were ever compromised or misconfigured.

Suggested change
final String spiffeId = extractSpiffeIdFromToken(subjectToken);
if (spiffeId != null) {
accessToken.setCustomClaim(IdToken.CLAIM_SPIFFE, spiffeId);
}
final String spiffeId = extractSpiffeIdFromToken(subjectToken);
if (spiffeId != null) {
verifySpiffeIdMatchesAthenzPrincipal(spiffeId, subjectToken.getSubject(), caller, requestDomainName, principalDomain);
accessToken.setCustomClaim(IdToken.CLAIM_SPIFFE, spiffeId);
}

ctyano added 3 commits March 22, 2026 16:37
Signed-off-by: Tatsuya Yano <tatyano@lycorp.co.jp>
Signed-off-by: CTY <christopher.t.yano@gmail.com>
Signed-off-by: ctyano <ctyano@duck.com>
- Add explicit authorization check for source->target token exchange
- Fix ID token exchange access check to validate the caller principal
- Update unit tests to include token source exchange policy

Signed-off-by: CTY <christopher.t.yano@gmail.com>
Signed-off-by: ctyano <ctyano@duck.com>
Signed-off-by: CTY <christopher.t.yano@gmail.com>
Signed-off-by: ctyano <ctyano@duck.com>
@ctyano ctyano force-pushed the zts-id-token-exchange-jwt-svid-spiffe branch from f6993fb to 0a81720 Compare March 22, 2026 07:37
Signed-off-by: ctyano <ctyano@duck.com>
// out token delegation from source to target domain

final String resource = sourceDomainName + ":" + requestDomainName;
if (!authorizer.access(ZTSConsts.ZTS_ACTION_TOKEN_SOURCE_EXCHANGE, resource, principal, null)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this authorization check?

First, we've already verified at the beginnig of the method that princpal name is the same as the subject in the actor token.
Next, when validating the actor subject token in the token request parser, we've already verified that the subject token includes the mayAct claim and the value matches to the subject of the actor token. So the authorization has already been granted and verified so not sure we're adding yet another authorization check here?

return null;
}

final Object spiffeClaim = token.getClaim(IdToken.CLAIM_SPIFFE);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the claim itself is called spiffe but that's only valid for ZTS itself. If we're doing an exchange from another identity provider, then most likely this isn't going to work.

if it's a spiffe svid, then spiffie uri is in the sub claim and that's supported.

So if it's any other claim used by that identity provider then it's not supported. This may not be a problem if we're only dealing with ZTS tokens or spiffe svids but something to be aware of. maybe we can add comments about this limitation

Principal subjectPrincipal = createPrincipalForName(subjectIdentity);
for (String idTokenGroup : idTokenGroups) {
if (!authorizer.access(ZTSConsts.ZTS_ACTION_ID_TOKEN_EXCHANGE, idTokenGroup, subjectPrincipal, null)) {
if (!authorizer.access(ZTSConsts.ZTS_ACTION_ID_TOKEN_EXCHANGE, idTokenGroup, principal, null)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this changed? it's an incorrect change.

We're checking if the principal in the subject token is authorized for the change not the principal who requested the exchange (which could be some proxy token exchange broker).

a few lines above, we verify that the principal in the request is authorized to request the token exchange on behalf of the subject principal and here we must verify that subject principal is authorized for the requested role

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants