Repository navigation
feat(oauth2): support certificate-bound OAuth2 tokens and mTLS rotation for Workload Identity Federation - #14781
macastelaz wants to merge 11 commits into
Conversation
…#13955) ## Summary Adds support for actor tokens in `IdentityPoolCredentials` during certificate-bound OAuth2 STS token exchange, and implements per-cycle mTLS certificate snapshotting and single-retry rotation. --- ## Architectural Changes & Review Feedback Resolutions ### 1. Actor Token Extraction & Atomic Reads - Added package-private `IdentityPoolActorTokenSupplier` interface. - Enhanced `FileIdentityPoolSubjectTokenSupplier` with `readTokens()` to read and parse JSON credential files in a single atomic I/O operation, returning a `TokenPair` for subject and actor tokens. - Removed unreliable file timestamp caching; the file is read at most once per refresh cycle. - Added early validation in `IdentityPoolCredentialSource`: `actorTokenFieldName` must be non-empty, distinct from `subjectTokenFieldName`, and is supported only on JSON file sources. ### 2. Per-Cycle Certificate Pinning & 401 Rotation Retry - `refreshAccessToken()` snapshots the `KeyStore` from `X509Provider` at the start of each refresh cycle, creating a cycle-isolated `MtlsHttpTransportFactory` passed to the STS exchange. - On 401 Unauthorized from STS, `refreshAccessToken()` re-reads a fresh `KeyStore` from `x509Provider` and retries the exchange once (preserving original error context via `addSuppressed` if cert reload fails). - Relaxed mTLS endpoint validation to check for `.mtls.` to support custom universes and PSC endpoints. ### 3. Serialization & Binary Backward Compatibility - **SUID Compatibility**: Preserved `FileIdentityPoolSubjectTokenSupplier` class name and declared static `serialVersionUID = 7152208690659890358L;`, matching the synthetic SUID generated before this PR to prevent `InvalidClassException` on existing serialized credentials. - **Binary ABI Preservation**: Kept `public NetHttpTransport create()` return type on `MtlsHttpTransportFactory` to preserve binary compatibility for compiled callers via covariant return typing. - **Post-Deserialization Usability**: Implemented custom `readObject()` in `IdentityPoolCredentials` to reconstruct transient `x509Provider` and `MtlsHttpTransportFactory` from the serialized `credentialSource`, ensuring `refreshAccessToken()` and `createScoped()` remain fully functional after deserialization. - **KeyStore Validation**: `MtlsHttpTransportFactory.hasKeyStore()` checks `mtlsKeyStore != null && mtlsKeyStore.size() > 0`, ensuring uninitialized or empty KeyStores are not treated as mTLS-configured. ### 4. API Visibility - Made `setActorTokenSupplier`, `setActorTokenType`, and `setX509Provider` package-private on `IdentityPoolCredentials.Builder` for internal/testing use. --- ## Test Coverage - **1009 unit tests passing** (`oauth2_http` module). - **Production Path Tests**: End-to-end tests parsing JSON configs via `ExternalAccountCredentials.fromStream()` and executing `refreshAccessToken()`. - **Serialization Tests**: Verified backward compatibility against pre-PR serialized byte fixtures and verified token refresh on deserialized instances. - **Cert Pinning & Retry Tests**: Verified single-snapshot cert consistency and 401 cert rotation retries. - **Google Java Format**: Verified 100% compliance with `com.spotify.fmt:fmt-maven-plugin:2.25`. ### Manual Testing - Verified end-to-end execution against live Google STS endpoints, token exchange with actor tokens over mTLS, scoping, serialization round-trip with `readObject()` restoration, and multi-cycle refreshes. - Sample Test App: https://paste.googleplex.com/6209850244726784 - Sanitized Verification Output: https://paste.googleplex.com/6325994750345216 ## Next Steps The next phase (which will follow in a separate PR to keep reviews scoped) will introduce dynamic mTLS transport rotation natively in GAX and a 401 connection-draining interceptor required for downstream service retry logic. Based on design: https://docs.google.com/document/d/1NIKeJX86ETNAjoQA-lZIE_G_8mHspisBaL8gwNCO3dA/edit?resourcekey=0-z7teBZZk0WFIJEHL2ODZxw&tab=t.0 See b/542238030 for tracking
Removes a duplicate `google-cloud-storage` dependency declaration in `java-bigquery/google-cloud-bigquery/pom.xml`. The duplicate entry caused Maven project model validation failures during CI builds. The other declaration is in line 34 - 37.
…lters for flatten-maven-plugin (#14598) ## Summary Fixes `java.lang.VerifyError` in downstream tests (such as `java-bigquery-jdbc`) on GitHub Actions runners upgraded to **Apache Maven `3.10.0`**. ## Cause Maven `3.10.0` (Maven Resolver `2.0.24`) enables `aether.remoteRepositoryFilter.prefixes` and `aether.remoteRepositoryFilter.groupId` by default. Because `flatten-maven-plugin:1.3.0` reads dependency POMs with `repositories = null` (`[]`), the active remote repository filter rejects cached POMs in `~/.m2/repository`, causing `flatten-maven-plugin` to drop `<exclusions>` (such as `protobuf-javalite` on `io.grpc:grpc-protobuf-lite`) when generating `.flattened-pom.xml`. ## Fix Disable the default remote repository filters in `.mvn/maven.config`: ```text -Daether.remoteRepositoryFilter.prefixes=false -Daether.remoteRepositoryFilter.groupId=false ```
…patibility (#14609) Support for '#' comments in .mvn/maven.config was introduced in Apache Maven 3.9.0 (MNG-7682). Prior versions of Maven (such as 3.8.x) and IDE Maven parsers split the entire file on whitespace and reject '#' comment tokens with: Unable to parse maven.config: Unrecognized maven.config entries: [#, TODO:, ...] Remove the comment line from .mvn/maven.config so that it parses cleanly across both Maven 3.8.x and 3.9+.
…401 recovery (#14212) ### 🥞 Stacked PRs - 👉 **This PR** (*feat: implement IAM impersonation mTLS transport pinning and 401 recovery*) - ⏳ Stacked PR #14220 (*test: add mTLS in-process socket tests and live GCP WIF integration tests*) --- ## Summary Propagates the cycle-pinned mTLS transport factory into `ImpersonatedCredentials` during Workload Identity Federation with Service Account Impersonation, ensuring the IAM `generateAccessToken` call uses the exact same pinned client certificate as the STS token exchange. Also extends 401 recovery in `IdentityPoolCredentials` to catch unauthorized responses across both STS and IAM calls, reloading fresh certificates and re-executing the full refresh cycle. ## Test Coverage - **1,030 unit tests passing** across `oauth2_http` (including transport pinning lifecycle, multi-cycle rotation, 401 retry on IAM, and suppressed error handling). - **Google Java Format**: 100% compliant (`com.spotify.fmt:fmt-maven-plugin:2.25`). ### Manual Testing Sample test "app": https://paste.googleplex.com/5495919190081536 Test results: https://paste.googleplex.com/5757145073713152 See b/542238030 for tracking.
…ration tests (#14220) ## Description This PR adds comprehensive integration tests for Certificate-Bound OAuth 2.0 (mTLS), Actor Tokens, and Service Account Impersonation transport pinning across both hermetic in-process socket tests and live GCP Workload Identity Federation suites. ### Summary of Changes: 1. **Hermetic In-Process Socket Suite (`MtlsPipelineLocalTest.java`)**: - Uses JDK `HttpsServer` on `localhost` requiring client authentication (`setNeedClientAuth(true)`). - `testMtlsPipeline_verifiesPeerCertAndPayload`: Verifies server extracts client peer certificate from `SSLSession` and parses token exchange body (`grant_type`, `subject_token`, `actor_token`, `actor_token_type`). - `testMtlsPipeline_withImpersonation_usesSameCertForStsAndIam`: Verifies both STS and IAM calls execute on the exact same pinned mTLS transport and verifies the intermediate `Authorization: Bearer <token>` header. - `testMtlsPipeline_withImpersonation_401OnIam_retriesWithFreshCert`: Verifies full-cycle recovery when IAM returns 401 (reloads fresh KeyStore from disk, re-exchanges at STS with new cert, and calls IAM with new token + cert). - `testMtlsPipeline_concurrentRefreshes`: Multi-threaded refresh verifying thread-isolated transport snapshots. - `testMtlsPipeline_atomicTokenRead`: Verifies single-pass disk I/O for subject + actor tokens. 2. **Live GCP Suite Extensions (`ITWorkloadIdentityFederationTest.java`)**: - `identityPoolCredentials_withCertificateBoundWorkloadAndActorToken`: E2E verification of JSON config with certificate config and actor tokens. - `identityPoolCredentials_withProgrammaticMtlsAndActorToken`: E2E verification of programmatic builder configuration. 3. **`OAuthException.java`**: - Safely handles null HTTP error response content without NPE. ### Verification: - **Unit & Socket Tests**: 1,040 / 1,040 tests passing (including hermetic socket tests). - **Google Java Format**: 100% compliant (`mvn com.spotify.fmt:fmt-maven-plugin:2.25:check`). Source-Link: b/542238030
… IdentityPoolCredentials (#14430) ## Summary Allows `actor_token` and `actor_token_type` to be used in `IdentityPoolCredentials` with standard (non-mTLS) STS and IAM impersonation endpoints and without requiring client certificate (`certificate_config`) configuration. ## Context & Rationale In #13955, client-side guardrails (`isMtlsConfigured()` and `validateMtlsEndpoint()`) were enforced in the `IdentityPoolCredentials` constructor because Google STS initially required actor tokens to be paired with certificate-bound tokens over mTLS. Per the original design discussion, this was intentionally designed as a one-way door that could be loosened in a non-breaking manner once backend support for non-mTLS actor token exchanges was ready. ## Changes 1. **Removed Constructor mTLS Restrictions:** - Removed `isMtlsConfigured()` check in `IdentityPoolCredentials(Builder)` so actor tokens can be configured with standard `HttpTransportFactory` instances and without a `certificate` block. - Removed `validateMtlsEndpoint()` checks on `tokenUrl` and `serviceAccountImpersonationUrl`, allowing standard public endpoints (e.g., `https://sts.googleapis.com/v1/token`) to be used with actor tokens. - Preserved strict pairing validation between `actorTokenSupplier` and `actorTokenType`, as well as JSON format checks for file-based actor token extraction. 2. **401 Handling & Cert Rotation:** - When `x509Provider == null` (non-mTLS credentials), `refreshAccessToken()` uses standard transport without snapshotting a `KeyStore`, and `401 Unauthorized` errors propagate immediately without retry. - When `x509Provider != null && transportFactory instanceof MtlsHttpTransportFactory` (mTLS credentials), per-cycle certificate pinning and single-retry cert reload on `401` remain unchanged. 3. **Javadoc & Test Coverage:** - Updated class-level and builder Javadocs on `IdentityPoolCredentials`. - Updated negative constructor tests to positive verification tests (`*_succeeds`). - Added `refreshAccessToken_401WithActorTokenAndNonMtlsTransport_bubblesUpWithoutRetry` and `fromStream_fileCredentialSource_withoutCertificateConfig_andActorToken_withNonMtlsUrl_refreshesSuccessfully` to verify end-to-end non-mTLS actor token exchanges. ## Verification - All **1,035** unit tests passing in `oauth2_http` module (including all **88** tests in `IdentityPoolCredentialsTest`). - 100% compliant with `fmt-maven-plugin:2.25:check`. --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…ult (#14554) ### Summary Enables the 4 mTLS + `actor_token` Workload Identity Federation integration tests in `ITWorkloadIdentityFederationTest` to run by default in CI without requiring `GOOGLE_API_CERTIFICATE_CONFIG` or external Secret Manager secrets: 1. **Default mTLS Certificate Config Fallback:** Falls back to the checked-in `testresources/mtls/certificate_config.json` (`test_cert.pem` / `test_key.pem`) when `GOOGLE_API_CERTIFICATE_CONFIG` is unset, removing the `assumeTrue` skip. 2. **Signed OIDC JWTs with `cnf.x5t#S256` and `may_act`:** Targets `oidc-jwt-1` (`OIDC_JWT_AUDIENCE`) and uses the `GOOGLE_APPLICATION_CREDENTIALS` service account private key (whose public key is registered in `oidc-jwt-1`'s `jwksJson`) to sign OIDC JWTs containing RFC 8705 certificate thumbprint binding (`cnf.x5t#S256`) and delegation (`may_act`) claims.
There was a problem hiding this comment.
Code Review
This pull request introduces support for actor tokens in Workload Identity Federation, enabling certificate-bound workloads and delegation. It updates IdentityPoolCredentials and ImpersonatedCredentials to handle atomic subject and actor token reads, per-cycle mTLS transport pinning, and robust 401/400 retry mechanisms with certificate reloading. Additionally, MtlsHttpTransportFactory is made serializable, and extensive hermetic tests are added to validate the mTLS pipeline. Feedback on the changes suggests wrapping keyStore.getKey in a nested try-catch block within OAuth2Utils to prevent password mismatches from disabling token caching, and deferring InputStreamReader instantiation in FileIdentityPoolSubjectTokenSupplier to avoid consuming bytes before JSON parsing.
…e-bound tokens" This reverts commit a2778ef.
Summary
Merges the
oauth2-bound-tokensfeature branch intomain, adding end-to-end support for certificate-bound OAuth 2.0 tokens and automatic mTLS certificate rotation in Workload Identity Federation (IdentityPoolCredentialsandImpersonatedCredentials).Included Feature Branch PRs
feat(oauth2): Extract actor tokens for cert-bound OAuth2 STS exchangefeat(oauth2): implement IAM impersonation mTLS transport pinning and 401 recoverytest(oauth2): add mTLS in-process socket tests and live GCP WIF integration testsfeat(oauth2): support non-mTLS token URLs and unbound actor tokens in IdentityPoolCredentialstest(auth): enable mTLS and actor_token WIF integration tests by defaultKey Capabilities
IdentityPoolActorTokenSupplier,FileIdentityPoolSubjectTokenSupplier):actor_token(text or JSON viaactor_token_field_nameandactor_token_type) for STS token exchange.mtime, size, file key) and JWTexpclaims with a 5-minute expiration margin to avoid redundant disk reads.IdentityPoolCredentials,ImpersonatedCredentials,MtlsHttpTransportFactory):KeyStoreonce per refresh cycle so leaf certificate extraction, the STS token exchange, and downstream IAM Service Account impersonation (generateAccessToken/generateIdToken) share the exact same certificate and private key snapshot.401 Unauthorized, OAuthinvalid_grant, and transient TLS/KeyStore load failures during non-atomic split cert/key writes by reloading theKeyStoreonce and retrying when a certificate change is detected (OAuth2Utils.hasCertificateChanged).ImpersonatedCredentialsconfigured withMtlsProvideras well as delegatedImpersonatedCredentialschains.IdentityPoolCredentialsTest,ImpersonatedCredentialsTest,FileIdentityPoolSubjectTokenSupplierTest,MtlsHttpTransportFactoryTest,OAuth2UtilsTest, andOAuthExceptionTest.MtlsPipelineLocalTest) and live GCP Workload Identity Federation integration tests (ITWorkloadIdentityFederationTest).