Repository navigation
fix(gax): drop jsr305 annotations left over after syncing with main - #14626
Merged
macastelaz merged 1 commit intoOct 14, 2026
Conversation
main no longer declares com.google.code.findbugs:jsr305 for gax-grpc and gax-httpjson, so the two javax.annotation uses added by the certificate rotation work fail dependency:analyze (used undeclared dependency): - ChannelPool: replace @GuardedBy (javax.annotation.concurrent) with a comment; gax-grpc does not depend on Error Prone annotations. - RefreshingHttpJsonChannelTest: use org.jspecify.annotations.Nullable, as the rest of gax does.
Contributor
There was a problem hiding this comment.
Code Review
This pull request replaces the javax.annotation.concurrent.GuardedBy annotation with a comment in ChannelPool.java and updates RefreshingHttpJsonChannelTest.java to use org.jspecify.annotations.Nullable instead of javax.annotation.Nullable. There are no review comments, and I have no additional feedback to provide.
lsirac
approved these changes
Oct 14, 2026
macastelaz
merged commit Oct 14, 2026
4aad503
into
googleapis:agentic-identities-bound-token
249 checks passed
macastelaz
added a commit
that referenced
this pull request
Oct 19, 2026
…r agent identities (#14668) ## Summary Merges the `agentic-identities-bound-token` feature branch into `main`, adding end-to-end support for certificate-bound tokens and automatic mTLS with transparent certificate rotation for agent identities on Cloud Run and GKE. ### `google-auth-library-java` (`oauth2_http`) - **Default certificate-bound tokens (`#13873`, `#14595`, `#14658`):** When an agent identity SPIFFE workload certificate is present (either via `GOOGLE_API_CERTIFICATE_CONFIG` on Cloud Run or the GKE credential bundle at `/var/run/secrets/workload-spiffe-credentials/x509.credential-bundle.private-key.pem`), `ComputeEngineCredentials` requests certificate-bound access tokens and ID tokens via `POST` to the Metadata Server with the leaf certificate chain. - **Opt-out controls (`#13873`, `#14559`, `#14557`):** - Process-wide: `GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false` (or legacy `GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES=false`) disables token binding. - Per-target ID tokens: `IdTokenProvider.Option.DISABLE_BOUND_ID_TOKEN` requests an unbound ID token for specific targets (such as plain HTTPS `*.run.app` URLs) while keeping other ID tokens and access tokens bound. - **Automatic mTLS & strict configuration (`#13995`, `#14595`, `#14658`):** - `MtlsUtils.useMtlsClientCertificate()` enables mTLS automatically when a workload certificate config or GKE credential bundle is present, unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to a non-`"true"` value. - Missing, unreadable, or malformed explicit `GOOGLE_API_CERTIFICATE_CONFIG` files fail closed with `IllegalStateException`. - **Full mTLS certificate chain (`#14621`):** `X509Provider` and `SecureConnectProvider` now populate the mTLS key store with the full certificate chain (leaf + intermediates) via `MtlsKeyStoreUtils.createMtlsKeyStore` rather than only the leaf certificate. ### `sdk-platform-java/gax-java` (`gax`, `gax-grpc`, `gax-httpjson`) - **Transparent retries on mTLS certificate rotation (`#13995`):** - `CertificateRotationTracker` checks the SHA-256 fingerprint of the workload certificate on disk after an `UNAUTHENTICATED` failure. - `ChannelPool` (gRPC) and `RefreshingHttpJsonChannel` (HTTP/JSON) rebuild transport channels with the rotated certificate without interrupting in-flight RPCs. - `AttemptCallable` (unary) and `ServerStreamingAttemptCallable` (server streaming) issue one immediate free retry on the refreshed channel when a certificate rotation is detected. - **Fail-closed default mTLS provider loading (`#14658`):** When `useMtlsClientCertificate()` is `true`, `EndpointContext`, `InstantiatingGrpcChannelProvider`, and `InstantiatingHttpJsonChannelProvider` propagate `IOException`s (while still falling back cleanly on `CertificateSourceUnavailableException` when no certificate source exists on disk). - **Conscrypt TLS 1.3 mTLS fix (`#14556`):** `InstantiatingHttpJsonChannelProvider` uses Conscrypt's own `TrustManagerFactory` and `KeyManagerFactory` for the mTLS `SSLContext`, fixing `Unknown authType: GENERIC` on TLS 1.3 and key-manager algorithm constraint mismatches on JDK 26+. ## Sub-PRs Included on `agentic-identities-bound-token` | PR | Description | | :--- | :--- | | `#13873` | `feat: Enable Bound Token for Agentic Identities` | | `#13995` | `feat(gax): support transparent retries during mTLS certificate rotations` | | `#14556` | `fix(gax-httpjson): use Conscrypt TrustManagerFactory for mTLS SSLContext` | | `#14559` | `feat(auth): add DISABLE_BOUND_ID_TOKEN option to opt out of certificate-bound ID tokens` | | `#14557` | `docs(auth): document certificate-bound tokens for agent identities` | | `#14595` | `feat(auth): support GKE agent identity credential bundle for bound tokens and mTLS` | | `#14626` | `fix(gax): drop jsr305 annotations left over after syncing with main` | | `#14621` | `fix(auth): present the full certificate chain for mTLS` | | `#14658` | `fix(auth,gax): align Java mTLS and agent identity certificate handling with Python parity` | ## Testing - **Automated tests:** Full unit test suites in `google-auth-library-java` (`credentials`, `oauth2_http`) and `sdk-platform-java/gax-java` (`gax`, `gax-grpc`, `gax-httpjson`) pass across JDK 8, 11, 17, 21, 25, and 26, including new unit tests for certificate discovery, bound token requests, startup polling, rotation retries, Conscrypt TLS 1.3 mTLS handshakes, and full certificate chains. - **Integrated manual testing (`45/45` scenarios passed):** Built all 9 changed artifacts (`google-auth-library-credentials`, `google-auth-library-oauth2-http`, `api-common`, `gax`, `gax-grpc`, `gax-httpjson`, `google-cloud-core`, `google-cloud-core-http`, `google-cloud-core-grpc`) together from the combined branch and ran 22 live GAPIC mTLS rotation scenarios, 14 local bound-token and GAX parity checks, 3 live Cloud Run Agent Identity job executions, and 6 live GKE Agent Identity pod scenarios. <details> <summary>Full manual test matrix (45 scenarios)</summary> ### 1. Live GAPIC mTLS Rotation (`22/22` Scenarios, `125/125` Checks) Executed real `KeyManagementServiceClient` (gRPC and HTTP/JSON) and `BigQueryReadClient` (server-streaming `ReadRows`) against live `.mtls.googleapis.com` endpoints (`cloudkms.mtls.googleapis.com`, `bigquerystorage.mtls.googleapis.com`) while rotating client certificates on disk. | Group | Scenarios | What Was Verified | Result | | :--- | :--- | :--- | :--- | | **Unary gRPC (`G1`–`G7`)** | `atomic-mv`, `inplace`, `symlink-swap`, `corrupt-recover`, `missing-recover`, `idle-gap`, `burst-25-rpcs` | `KeyManagementServiceClient` gRPC channel seamlessly picks up rotated certs across atomic rename, in-place write, Kubernetes-style symlink swap, corrupt/missing file recovery, idle gaps, and concurrent bursts. | **7/7 Passed** | | **Server-Streaming gRPC (`S1`–`S4`)** | `mid-stream`, `between-streams`, `reconnect-after-rotate`, `corrupt-mid-stream` | `BigQueryReadClient.readRowsCallable()` active streams continue uninterrupted during rotation; subsequent streams and reconnects use the new certificate. | **4/4 Passed** | | **Unary HTTP/JSON (`H1`–`H5`)** | `atomic-mv`, `inplace`, `corrupt-recover`, `missing-recover`, `burst-20-rpcs` | `KeyManagementServiceClient` HTTP/JSON transport reloads rotated certs and recovers from transient corrupt/missing files without dropping requests. | **5/5 Passed** | | **Startup & Providers (`N1a`, `N1b`, `C1`)** | `N1a`, `N1b`, `C1-conscrypt`, `C1-jdk` | Missing cert config handling at startup; full rotation works under both Conscrypt and JDK (`SunJSSE`) TLS providers. | **4/4 Passed** | | **Combined Assertions** | `G1`–`S4` assertions | Verified `125/125` individual assertions across 22 isolated JVMs. | **22/22 Passed** | ### 2. Local Bound-Token & GAX Parity (`14/14` Checks) | Check | PR / Area | What Was Verified | Result | | :--- | :--- | :--- | :--- | | **Scenarios 1–7** | `#13873` / `#14595` | Default bound token (`cnf.x5t#S256` matches leaf), `GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false`, `GOOGLE_API_USE_CLIENT_CERTIFICATE=false`, cert rotation updates `x5t#S256`, and `GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES` behavior. | **Passed** | | **Scenario 8d** | `#14658` | `GOOGLE_API_USE_CLIENT_CERTIFICATE=true` with no certs falls back immediately (`43ms` GET) with no 30s polling delay. | **Passed** | | **Scenario 8e** | `#14658` | Invalid explicit `GOOGLE_API_CERTIFICATE_CONFIG` fails closed with `IOException` instead of silently downgrading. | **Passed** | | **Scenario 10** | `#14658` | 0-byte startup cert file is polled until populated rather than failing immediately. | **Passed** | | **Scenario 11** | `#14658` | Kubernetes `..data` symlinked parent directory resolves to the real directory for file watching. | **Passed** | | **Scenario 12a** | `#14559` | `IdTokenProvider.Option.DISABLE_BOUND_ID_TOKEN` returns an unbound ID token without leaking state to subsequent default calls. | **Passed** | | **Scenario 12b** | `#14621` | `X509Provider` preserves the full 2-certificate chain (`leaf + intermediate`) in the `KeyStore`. | **Passed** | | **GAX `fail-closed`** | `#14658` | Missing explicit `GOOGLE_API_CERTIFICATE_CONFIG` throws `IllegalStateException` in both `InstantiatingGrpcChannelProvider` and `InstantiatingHttpJsonChannelProvider`. | **Passed** | | **GAX `zero-ms-fallback`** | `#14658` | `GOOGLE_API_USE_CLIENT_CERTIFICATE=true` with no certs builds plain gRPC and HTTP/JSON channels immediately (`0ms` polling delay). | **Passed** | ### 3. Live Cloud Run Agent Identity (`3/3` Scenarios) Tested on a Cloud Run Agent Identity job (`--functional-type=agent --identity-type=agent-identity`). | Scenario | Env Vars | Key Results | | :--- | :--- | :--- | | **Default (Bound)** | *(unset)* | `DefaultMtlsProviderFactory` chain length = `2` (`#14621`). Access token acquired in `414ms`. ID token `cnf.x5t#S256` matched leaf cert. `DISABLE_BOUND_ID_TOKEN` (`#14559`) returned `cnf: false` and next default call restored `cnf: true`. GAX `ProjectsClient` (gRPC & HTTP/JSON) resolved `cloudresourcemanager.mtls.googleapis.com:443` and passed mTLS + bound-token auth (`PERMISSION_DENIED` IAM check). Non-mTLS transport rejected bound token (`401`), true mTLS passed auth (`403`), and mismatched client cert was rejected (`401`). | | **Runtime Bound Opt-Out** | `GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false` | Unbound access token and ID token (`cnf` absent). Non-mTLS endpoints passed auth (`403` IAM) and live Java ADK `LlmAgent` + `Gemini` call on Vertex AI succeeded (`200 OK`). | | **Client Cert Opt-Out** | `GOOGLE_API_USE_CLIENT_CERTIFICATE=false` | Unbound access token (`450ms`) and ID token (`cnf` absent). GAX `ProjectsClient` (gRPC & HTTP/JSON) resolved plain `cloudresourcemanager.googleapis.com:443` and passed auth (`PERMISSION_DENIED`). Live Java ADK `LlmAgent` + `Gemini` call on Vertex AI succeeded (`200 OK`). | ### 4. Live GKE Agent Identity (`6/6` Scenarios) Tested on a GKE Agent Identity cluster against live GCP APIs (`cloudresourcemanager`, `storage`) and an in-cluster SPIFFE mTLS receiver configured to trust **only** the SPIFFE root CA (requiring the full leaf + intermediate chain from `#14621`). | Scenario | Pod Annotations | Env Vars | Key Results | | :--- | :--- | :--- | :--- | | **`s1-default`** | `inject-podcertificates=true` | *(unset)* | `bundle exists=true`, first token fetch `1430ms`, SDK KeyStore chain length = `2`, mTLS receiver (SDK KeyStore) = `200 OK`, mTLS GCS passed auth (`404`), plain GCS rejected bound token (`401`), gRPC & HTTP/JSON resolved `.mtls.googleapis.com:443` and passed auth, `DISABLE_BOUND_ID_TOKEN` -> plain `200` / mTLS `401` / default after opt-out -> mTLS `200` / access token still bound -> plain GCS `401`. | | **`s2-runtime-bound-false`** | `inject-podcertificates=true` | `GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false` | Unbound tokens (`cnf=null`), mTLS receiver rejected unbound token (`401`), plain receiver accepted (`200`), raw GCS and Storage SDK (HTTP & gRPC) passed auth (`404`), gRPC & HTTP/JSON resolved `.mtls.googleapis.com:443` and passed auth. | | **`s3-use-cert-false`** | `inject-podcertificates=true` | `GOOGLE_API_USE_CLIENT_CERTIFICATE=false` | Unbound tokens (`cnf=null`), gRPC & HTTP/JSON resolved plain `cloudresourcemanager.googleapis.com:443` and passed auth, plain receiver = `200`, plain GCS passed auth (`404`). | | **`s4-use-cert-true`** | `inject-podcertificates=true` | `GOOGLE_API_USE_CLIENT_CERTIFICATE=true` | Identical bound behavior to `s1-default`: SDK KeyStore mTLS receiver = `200`, `.mtls.googleapis.com:443` resolved and passed auth, all `DISABLE_BOUND_ID_TOKEN` checks passed. | | **`s5-no-annotation`** | *(none)* | *(unset)* | `bundle exists=false`, first token fetch `646ms` (no 30s hang), resolved plain `cloudresourcemanager.googleapis.com:443`, passed auth (`PERMISSION_DENIED` / `403`). | | **`s6-use-cert-true-no-annotation`** | *(none)* | `GOOGLE_API_USE_CLIENT_CERTIFICATE=true` | Verified `#14658` on real GKE: `bundle exists=false`, first token fetch `609ms` (no 30s polling delay), resolved plain `cloudresourcemanager.googleapis.com:443`, passed auth (`PERMISSION_DENIED` / `403`). | </details> BEGIN_COMMIT_OVERRIDE feat(auth): request certificate-bound tokens for agent identities by default. Set GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false to opt out. feat(auth): add IdTokenProvider.Option.DISABLE_BOUND_ID_TOKEN to request unbound ID tokens for specific targets. feat(auth): support GKE agent identity credential bundle for bound tokens and mTLS. fix(auth): present the full certificate chain for mTLS. docs(auth): known limitation: ADK for Java doesn't support mTLS yet; agents that use it must set GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false. feat(gax): enable mTLS automatically when a workload certificate is present and support transparent retries during mTLS certificate rotations. fix(gax-httpjson): use Conscrypt TrustManagerFactory and KeyManagerFactory for mTLS SSLContext. END_COMMIT_OVERRIDE --------- Co-authored-by: Kirill Logachev <kirl@google.com> Co-authored-by: Min Zhu <zhumin@google.com> Co-authored-by: Lawrence Qiu <lawrenceqiu@google.com> Co-authored-by: yangyzs <171981480+yangyzs@users.noreply.github.com> Co-authored-by: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Co-authored-by: Keshav Dandeva <keshavdandeva@google.com> Co-authored-by: Mattie Fu <mattiefu@google.com> Co-authored-by: Will Howes <whowes@google.com> Co-authored-by: Jin Seop Kim <jinseop@google.com> Co-authored-by: Jiang Zhu <jiangzzhu@google.com> Co-authored-by: Knut Olav Løite <koloite@gmail.com> Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: rahul2393 <irahul@google.com> Co-authored-by: Sakthivel Subramanian <179120858+sakthivelmanii@users.noreply.github.com> Co-authored-by: Gaole Meng <gaole@google.com> Co-authored-by: Pranav Iyer <pjiyer@google.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
After the
mainsync (03af2000542), thedependenciescheck fails on every PR targeting this branch, for example #14559:mainmoved GAX offjsr305: it uses JSpecify for nullness, andgax-grpc/gax-httpjsonno longer declarejsr305. Twojavax.annotationuses that came in with #13995 (certificate rotation retries) are still on this branch. They compile, becausejsr305still arrives transitively, butdependency:analyzecorrectly flags them.What changes
These are annotation-only edits with no behavior change:
gax-grpc/.../ChannelPool.java: the@GuardedBy("entryWriteLock")annotation (fromjavax.annotation.concurrent) becomes a// Guarded by entryWriteLock.comment. I didn't switch to Error Prone's@GuardedBy, becausegax-grpcdoesn't depend on Error Prone annotations, so that would just trade one undeclared dependency for another. Onmain,gax-grpcuses no@GuardedBy.gax-httpjson/.../RefreshingHttpJsonChannelTest.java:javax.annotation.Nullablebecomesorg.jspecify.annotations.Nullable, as in the rest of GAX.These are the only
javax.annotationimports ingax-java,google-auth-library-javaandjava-corethat are on this branch but not onmain.Tests
dependency:analyzewas run the same way as.kokoro/dependencies.sh(mvn dependency:analyze -Pquick-build -DfailOnWarning=true -Dmdep.analyze.skip=false) ongax-grpcandgax-httpjson:Used undeclared dependencies found: com.google.code.findbugs:jsr305, BUILD FAILURE (the same as CI);Unit tests:
gax-grpc252 andgax-httpjson234 tests, 0 failures.fmtis clean.