Skip to content

feat(oauth2): support certificate-bound OAuth2 tokens and mTLS rotation for Workload Identity Federation - #14781

Open
macastelaz wants to merge 11 commits into
mainfrom
oauth2-bound-tokens
Open

macastelaz wants to merge 11 commits into
mainfrom
oauth2-bound-tokens

Conversation

@macastelaz

Copy link
Copy Markdown
Contributor

Summary

Merges the oauth2-bound-tokens feature branch into main, adding end-to-end support for certificate-bound OAuth 2.0 tokens and automatic mTLS certificate rotation in Workload Identity Federation (IdentityPoolCredentials and ImpersonatedCredentials).

Included Feature Branch PRs

Key Capabilities

  • Actor Token Support for Hybrid Certificate-Bound WIF (IdentityPoolActorTokenSupplier, FileIdentityPoolSubjectTokenSupplier):
    • Supports extracting both a primary X.509 leaf certificate subject token and a secondary file-sourced actor_token (text or JSON via actor_token_field_name and actor_token_type) for STS token exchange.
    • Caches file-sourced tokens using file metadata (mtime, size, file key) and JWT exp claims with a 5-minute expiration margin to avoid redundant disk reads.
  • Per-Cycle mTLS Transport Pinning & Certificate Rotation (IdentityPoolCredentials, ImpersonatedCredentials, MtlsHttpTransportFactory):
    • Snapshots the KeyStore once 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.
    • Automatically recovers from 401 Unauthorized, OAuth invalid_grant, and transient TLS/KeyStore load failures during non-atomic split cert/key writes by reloading the KeyStore once and retrying when a certificate change is detected (OAuth2Utils.hasCertificateChanged).
    • Supports standalone ImpersonatedCredentials configured with MtlsProvider as well as delegated ImpersonatedCredentials chains.
  • Test Coverage:
    • Comprehensive unit tests across IdentityPoolCredentialsTest, ImpersonatedCredentialsTest, FileIdentityPoolSubjectTokenSupplierTest, MtlsHttpTransportFactoryTest, OAuth2UtilsTest, and OAuthExceptionTest.
    • In-process mTLS socket tests (MtlsPipelineLocalTest) and live GCP Workload Identity Federation integration tests (ITWorkloadIdentityFederationTest).

macastelaz and others added 10 commits September 16, 2026 20:54
…#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.
@macastelaz
macastelaz requested review from a team as code owners October 27, 2026 02:32

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

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.

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.

@macastelaz
macastelaz requested a review from lqiu96 October 27, 2026 13:46

This branch has not been deployed

No deployments
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