Skip to content

Conversation

dimpavloff
Copy link

@dimpavloff dimpavloff commented Jul 7, 2025

Part one for grpc/proposal#492 (A97).
This is done in a new credentials/jwt package to provide file-based PerRPCCallCredentials. It can be used beyond XDS. The package handles token reloading, caching, and validation as per A97 .

There will be a separate PR which uses it in xds/bootstrap.

Whilst implementing the above, I considered credentials/oauth and credentials/xds packages instead of creating a new one. The former package has NewJWTAccessFromKey and jwtAccess which seem very relevant at first. However, I think the jwtAccess behaviour seems more tailored towards Google services. Also, the refresh, caching, and error behaviour for A97 is quite different than what's already there and therefore a separate implementation would have still made sense.
WRT credentials/xds, it could have been extended to both handle transport and call credentials. However, this is a bit at odds with A97 which says that the implementation should be non-XDS specific and, from reading between the lines, usable beyond XDS.
I think the current approach makes review easier but because of the similarities with the other two packages, it is a bit confusing to navigate. Please let me know whether the structure should change.

Relates to istio/istio#53532

RELEASE NOTES:

  • credentials: add credentials/jwt package providing file-based JWT PerRPCCredentials (A97)

Copy link

codecov bot commented Jul 7, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.97%. Comparing base (dd718e4) to head (330d9a8).
⚠️ Report is 56 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8431      +/-   ##
==========================================
- Coverage   82.27%   81.97%   -0.31%     
==========================================
  Files         414      414              
  Lines       40424    40587     +163     
==========================================
+ Hits        33259    33271      +12     
- Misses       5795     5930     +135     
- Partials     1370     1386      +16     
Files with missing lines Coverage Δ
credentials/jwt/jwt_file_reader.go 100.00% <100.00%> (ø)
credentials/jwt/jwt_token_file_call_creds.go 100.00% <100.00%> (ø)

... and 250 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dimpavloff dimpavloff changed the title xds: implement file-based JWT authentication (A97) xds: implement file-based JWT Call Credentials (A97) Jul 7, 2025
@dimpavloff
Copy link
Author

@dfawley hey 👋 Given you approved A97, would you mind having a cursory look at the PR to confirm if at least at a high level the approach looks good?

@eshitachandwani
Copy link
Member

I will take a look at this , I need to go through the gRFC first.

@dfawley dfawley self-assigned this Jul 22, 2025
@dfawley dfawley requested review from easwars and eshitachandwani and removed request for dfawley July 25, 2025 20:39
@dfawley dfawley assigned easwars and unassigned dfawley Jul 25, 2025
@dfawley
Copy link
Member

dfawley commented Jul 25, 2025

Sorry for the delay here.

@easwars would you be able to review this change? I think you have more background into some of the things than I do, like the bootstrap integration. Thank you!

@easwars
Copy link
Contributor

easwars commented Jul 28, 2025

Thank you for your contribution @dimpavloff. Yes, it would be nice if you can split this into smaller PRs. I will continue to use this PR to review the JWT call credentials implementation. If you can move the xDS implementation out to one or more PRs, I would greatly appreciate that and would be happy to review them as well.


// Verify cached expiration is 30 seconds before actual token expiration
impl := creds.(*jwtTokenFileCallCreds)
impl.mu.RLock()
Copy link
Contributor

Choose a reason for hiding this comment

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

Please only test the API surface. Relying on implementation internals in tests makes them brittle and would result in test changes when any changes to implementation is made.

Copy link
Author

Choose a reason for hiding this comment

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

I assume you are referring to using a private field rather than obtaining mu specifically.
In general I agree -- white box tests may get fragile and break during a refactor. However, this test and the next couple of ones are about the caching behaviour -- it is meant to be transparent to the external API. If I don't make assertions about the private fields, the tests may pass trivially and become more flaky (e.g. when testing the backoff in the next test).
One alternative could be factoring out these behaviours out into a separate private struct with "public" functions which expose the same information. Given that it would require shifting the majority of the implementation into that struct, I'm not sure it is an improvement from the current approach.
Please do let me know your thoughts and if you have other suggestions.

@easwars easwars assigned dimpavloff and unassigned easwars Jul 28, 2025
Copy link
Author

@dimpavloff dimpavloff left a comment

Choose a reason for hiding this comment

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

@easwars , I managed to iterate on top of your suggestion to simplify it further, we no longer need the condition variable, just one lock! LMK what you think

@dimpavloff dimpavloff changed the title xds: implement file-based JWT Call Credentials (A97) credentials: implement file-based JWT Call Credentials (part 1 for A97) Aug 22, 2025
@dimpavloff dimpavloff requested a review from easwars August 22, 2025 11:27
@dimpavloff dimpavloff removed their assignment Aug 22, 2025
@easwars easwars self-assigned this Aug 25, 2025
Copy link
Contributor

@easwars easwars left a comment

Choose a reason for hiding this comment

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

Looks quite good. Most of the comments are nits.

I still have to review some of the tests in credentials/jwt/jwt_token_file_call_creds_test.go though (starting with TestTokenFileCallCreds_CacheExpirationIsBeforeTokenExpiration).

if err == nil {
t.Fatalf("GetRequestMetadata() expected error, got nil")
}
if !strings.Contains(err.Error(), tt.wantErrContains) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Here as well, we should be checking for the status code instead of the actual contents of the error string.

Also, using status.Code(err) would work with nil error and return status.OK which would make the test logic much simpler as it would mostly the same for success and failure cases.

Copy link
Author

Choose a reason for hiding this comment

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

Nice point, updated.

Btw, this has helped uncover that credentials.CheckSecurityLevel and its call in GetRequestMetadata doesn't return a grpc code. This is also true in credentials/oauth/oauth.go and credentials/sts/sts.go. Therefore, this will map to codes.Unknown is this appropriate?

Copy link
Contributor

Choose a reason for hiding this comment

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

Will check with folks on the team and will get back to you soon on this. Thanks.

@easwars easwars assigned dimpavloff and unassigned easwars Aug 25, 2025
@dimpavloff dimpavloff requested a review from easwars August 26, 2025 17:52
@dimpavloff dimpavloff removed their assignment Aug 26, 2025
@easwars easwars self-assigned this Aug 29, 2025
Copy link
Contributor

@easwars easwars left a comment

Choose a reason for hiding this comment

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

Thanks for taking care of all of my comments. I think I'm mostly happy with where we are at now. I'll follow up on the two open issues and get back to you soon.

}
}

// createTestJWT creates a test JWT token with the specified audience and
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: remove mention of audience from the comment.

}
}

// ReadToken reads and parses a JWT token from the configured file.
Copy link
Contributor

Choose a reason for hiding this comment

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

Will check with folks on the team and will get back to you soon on this. Thanks.

if err == nil {
t.Fatalf("GetRequestMetadata() expected error, got nil")
}
if !strings.Contains(err.Error(), tt.wantErrContains) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will check with folks on the team and will get back to you soon on this. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Auth Includes regular credentials API and implementation. Also includes advancedtls, authz, rbac etc. Type: Feature New features or improvements in behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants