Skip to content

Implement gRFC A97: xDS JWT Call Credentials - #12951

Open
khush2520 wants to merge 13 commits into
grpc:masterfrom
khush2520:a97-revised
Open

Implement gRFC A97: xDS JWT Call Credentials#12951
khush2520 wants to merge 13 commits into
grpc:masterfrom
khush2520:a97-revised

Conversation

@khush2520

Copy link
Copy Markdown

Implements gRFC A97 to support file-based JSON Web Token (JWT) Call Credentials for xDS-enabled clients. Implemented using an experimental coding agent.
Summary of Changes

  • Core JWT Capabilities: Added JwtTokenFileCallCredentials in grpc-auth for lazy-loading, caching, exp-claim validation, and asynchronous token refresh via ExponentialBackoffPolicy.
  • xDS Bootstrap Parser Integration: Updated BootstrapperImpl and ServerInfo in grpc-xds to parse call_creds JSON lists, combining them utilizing composite credentials. Flag-guarded securely behind GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS.
  • Testing: Added rigorous unit tests and an end-to-end XdsJwtCallCredsIntegrationTest verifying real client-to-server TLS + JWT Authorization transmission.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jul 29, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

@shivaspeaks
shivaspeaks requested review from AgraVator and shivaspeaks and removed request for AgraVator July 30, 2026 06:22

@shivaspeaks shivaspeaks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sending what I have with the high level review.

Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java Outdated
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java
Comment thread xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java Outdated
Comment thread xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java
Comment thread xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java Outdated
Comment thread xds/src/test/java/io/grpc/xds/XdsJwtCallCredsIntegrationTest.java Outdated
@shivaspeaks

Copy link
Copy Markdown
Member

/gemini review

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

Copy link
Copy Markdown

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 introduces JwtTokenFileCallCredentials to load, parse, cache, and refresh JWT tokens from a file, and integrates it into the xDS bootstrap configuration to support call_creds. The review feedback identifies two bugs in JwtTokenFileCallCredentials: a critical issue where valid cached tokens are ignored during backoff after a failed background refresh, and a potential integer overflow when calculating expirationTimeMillis from large exp claims.

Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java
khush2520 and others added 5 commits July 31, 2026 09:35
Add a defensive check to prevent overflow by capping the expiration time at Long.MAX_VALUE if expSeconds is too large.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@khush2520

Copy link
Copy Markdown
Author

/gemini review

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

Copy link
Copy Markdown

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 introduces JwtTokenFileCallCredentials to load, parse, and cache JWT tokens from a file, and integrates it into the xDS bootstrap configuration under an experimental flag. It also updates GrpcXdsTransportFactory to support composite call credentials. The review feedback highlights two important safety improvements in JwtTokenFileCallCredentials: using a bounded stream when reading the token file to prevent potential OutOfMemoryError on special devices, and adding a null check on the parsed JSON element to avoid a NullPointerException when handling malformed payloads.

Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/client/LoadStatsManager2.java

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.

No need for the new overload, as this method is now left with no other callers in non-test code. Just modify signature of existing method.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

While it has no production callers left, the 6-argument ServerInfo.create() is heavily used across dozens of test suites. Keeping it as a @VisibleForTesting overload prevents polluting all those test calls with unnecessary null values for CallCredentials.

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.

Modifying a lot of tests to switch over to the new method with CallCredentials is fine. The only cases where we prefer to have overloaded methods omitting certain arguments are if there are legitimate production code callers for it or if the method is public and we dont' want to break downstream projects. But Bootstrapper although public, is an @Internal class, so the 2nd consideration doesn't really apply here.

Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java Outdated
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java Outdated
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java Outdated
Comment thread xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java
Comment thread xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java
Comment thread auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java
@khush2520
khush2520 force-pushed the a97-revised branch 2 times, most recently from 1088069 to 535f3c9 Compare August 4, 2026 17:57

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.

Modifying a lot of tests to switch over to the new method with CallCredentials is fine. The only cases where we prefer to have overloaded methods omitting certain arguments are if there are legitimate production code callers for it or if the method is public and we dont' want to break downstream projects. But Bootstrapper although public, is an @Internal class, so the 2nd consideration doesn't really apply here.

Comment thread xds/src/main/java/io/grpc/xds/client/Bootstrapper.java
headers.put(AUTHORIZATION_HEADER, "Bearer " + tokenInfo.token);
for (MetadataApplier applier : appliersToApply) {
try {
applier.apply(headers);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Our Metadata.java is not threadsafe.
I believe every RPC should get its own distinct, unshared Metadata because if multiple RPCs process this shared Metadata instance concurrently, or if an interceptor mutates it, you will get data corruption

CC: @kannanjgithub WDYT?


if (hasValidCache) {
tokenToApply = new TokenInfo(cachedToken, expirationTimeMillis);
if (readState == ReadState.BACKOFF && now >= nextAttemptTimeMillis) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this redundant?
Because we already execute this exact check at the very beginning of the synchronized block and since this block executes under a lock, readState cannot possibly revert to ReadState.BACKOFF.

triggerRead = true;
}
} else {
if (readState == ReadState.BACKOFF && now >= nextAttemptTimeMillis) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same applies here.

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.

4 participants