Skip to content

docs(auth): document certificate-bound tokens for agent identities [blocked on #13873] - #14557

Draft
macastelaz wants to merge 36 commits into
googleapis:agentic-identities-bound-tokenfrom
macastelaz:docs-agent-bound-tokens
Draft

macastelaz wants to merge 36 commits into
googleapis:agentic-identities-bound-tokenfrom
macastelaz:docs-agent-bound-tokens

Conversation

@macastelaz

Copy link
Copy Markdown
Contributor

Important

Draft, blocked on #13873. This branch is based on #13873's head (f17f773a75c), because #13873 also edits ComputeEngineCredentials.java. Until #13873 merges, this PR also shows #13873's commits. Only the last commit (docs(auth): document certificate-bound tokens for agent identities) belongs to this PR. After #13873 merges I'll rebase onto agentic-identities-bound-token so the diff is docs-only.

Note

Open question: the team is considering shipping bound access tokens only and deferring bound ID tokens. If that happens, this PR will be updated to say "access tokens" only.

What this adds

Docs only; no code changes.

  • google-auth-library-java/README.md: new section "Certificate-bound tokens for agent identities":
    • Bound tokens are the default when an agent identity workload certificate is present.
    • Bound tokens need mTLS with the same certificate. GAX-based client libraries handle this automatically; custom HTTP clients must configure it themselves.
    • When a bound token is requested: certificate discovery through GOOGLE_API_CERTIFICATE_CONFIG or the default directory, and an agent identity SPIFFE trust domain.
    • Turning off binding: GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false, which takes precedence over the legacy GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES. GOOGLE_API_USE_CLIENT_CERTIFICATE=false also turns binding off. Values are case-insensitive, the opt-out applies to the whole process, and it's strongly discouraged.
    • Known limitation: ADK for Java doesn't use mTLS yet, so it hits a 401; the workaround is the opt-out.
  • ComputeEngineCredentials class Javadoc: the same behavior in brief, plus the fail-fast cases. If the certificate config points to missing files, or the metadata server doesn't support bound tokens, requests throw IOException instead of silently falling back to unbound tokens.

All statements were checked against AgentIdentityUtils (isTokenBindingEnabled, shouldEnableMtls, shouldRequestBoundToken) and ComputeEngineCredentials in #13873.

The Cloud Run public docs are being updated separately (internal CL), and the README links to https://cloud.google.com/run/docs/ai/authenticate-agents.

Release notes

google-auth-library-java/CHANGELOG.md is generated, so release notes come from commit messages. Suggested text for the BEGIN_COMMIT_OVERRIDE block of the feature-branch → main merge PR:

feat(auth): request certificate-bound tokens for agent identities by default. Set GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false to opt out.
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.

Testing

  • fmt-maven-plugin:check passes.
  • javadoc:javadoc on oauth2_http completes with no new warnings.

vverman and others added 30 commits July 29, 2026 03:35
1. POST request to MDS with cert-chain

2. Cert-key matching

3. Included logic to consider the user's choice by looking at GOOGLE_API_USE_CLIENT_CERTIFICATE env variable

4. Bound ID tokens.

# Conflicts:
#	google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java
#	google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
…etry logic.

Nit fixes.

# Conflicts:
#	google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java
# Conflicts:
#	google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
…n binding

- Prevent 30-second polling delay and IOException on standard GCE/container environments when well-known credentials directory exists without certificate files unless mTLS is explicitly enabled.
- Fail fast on malformed certificate config JSON without retrying.
- Fix URI resource path decoding in AgentIdentityUtilsTest to prevent FileNotFoundException when workspace paths contain spaces.
- Copy matching private key in well-known fallback test to verify full key pair loading.
- Clean up static wellKnownDir state and temporary directories in ComputeEngineCredentialsTest.
- Correct opt-out environment variable value in test setup from 'true' to 'false'.
…n binding

- Replace placeholder Javadoc comments across AgentIdentityUtils and CertInfo with descriptive documentation.
- Inline polling interval calculations via getSleepIntervalMs(), removing the static POLLING_INTERVALS list.
- Support GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN as primary environment variable with fallback to GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES.
- Add early check for GOOGLE_API_USE_CLIENT_CERTIFICATE=false in getAgentIdentityCertInfo() to exit without polling.
- Parse certificate and private key blocks separately using regex to strip private keys from HTTP payloads and enable cryptographic key-pair verification on combined bundle files.
- Throw IOException on AccessDeniedException for explicit configs and gracefully return null for implicit well-known discovery.
- Exit early without polling for non-workload configs (hasWorkloadConfig) and configs outside the well-known directory.
- Add non-production SPIFFE trust domain patterns (agents-nonprod) matching Python.
- Maintain IOException exception type on credential verification failures to preserve Credentials#refreshAccessToken interface contract.
- Validate full certificate content in ComputeEngineCredentialsTest.
- Use JUnit 5 @tempdir in AgentIdentityUtilsTest.
…d key validation

- Check isPathInWellKnownDir(paths.getCertPath()) after parsing workload config so startup polling is enabled even when GOOGLE_API_CERTIFICATE_CONFIG resides outside the well-known directory.
- Avoid throwing early on malformed config JSON when polling is active to handle non-atomic container startup writes.
- Record startup polling timeout (startupPollTimedOut) so subsequent token refreshes fail fast or fall back immediately without repeating 30-second sleep loops.
- Require a valid matching private key whenever loading Agent Identity credentials for token binding, removing the cert-only fallback branch.
- Check shouldRequestBoundToken(cert) immediately after parsing certificate content before reading private keys or executing signature verification.
- Cache verified CertInfo (and negative non-agent SPIFFE results) across token refreshes based on file metadata (mtime, size, fileKey), invalidating on file rotation.
- Make static fields (wellKnownDir, envReader, timeService) volatile and restrict EnvReader and setEnvReader visibility to package-private.
- Use JUnit 5 @tempdir parameter in AgentIdentityUtilsTest and add unit tests covering all new behaviors.
…comments

- Remove unrequested lastVerifiedCertInfo, hasValidBoundTokenCache helper,
  stat-reuse ternaries, and ECDSA KeyFactory normalization.
- Restore original keyPath fail-fast, initialStartupCompleted name, and
  exact-case algorithm checks (accepting EC and ECDSA per review).
- Make the internal loadAndVerifyCredentials overload private.
- Scope cached-credential fallbacks to their source: the config fallback
  requires a config-derived cache (and logs a warning), the well-known
  fallback requires a well-known cache.
- getLatestOrInitialCache only prefers the latest cache when it is a verified
  bound-token credential; reuse it in the loadAndVerifyCredentials fallback.
- tryUpdateCache: a config-only change mid-read returns the verified pair
  without caching it instead of forcing retries.
- checkExistsOrAccessDenied treats existing but unreadable files as
  AccessDeniedException.
- Stop polling when a workload config is missing cert_path or key_path.
- Normalize ECDSA-labelled EC public keys before initVerify.
- Reject unsupported key algorithms in readPrivateKey without retrying.
- Skip re-reading the config once mTLS is evaluated as explicitly disabled,
  and check the cached cert type before shouldEnableMtls in the fast path.
- Nits: message/comment wording, test split and sleep-count assertion.
…oogleapis#13873)

Adds a README section and ComputeEngineCredentials class Javadoc covering:
- bound tokens are the default when an agent identity workload certificate
  is present, and require mTLS with the same certificate
- when a bound token is requested (cert discovery, agent SPIFFE trust domain)
- opt-out env vars and precedence: GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN over
  the legacy GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES, and
  GOOGLE_API_USE_CLIENT_CERTIFICATE=false also disabling binding
- the process-wide scope of the opt-out
- the ADK for Java known limitation and its workaround

@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 certificate-bound access and ID tokens for agent identities within ComputeEngineCredentials. It adds a new AgentIdentityUtils utility class to manage certificate discovery, SPIFFE trust domain validation, cryptographic key-pair verification, and credential caching. ComputeEngineCredentials is updated to request bound tokens via POST requests to the metadata server when a workload certificate is available. Feedback on the changes suggests improving code readability in the test suite by avoiding fully qualified class names when the class is already imported.

Comment on lines +1407 to +1408
Files.write(keyPath, mismatchedKeyPem.getBytes(StandardCharsets.UTF_8));
Files.setLastModifiedTime(

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.

medium

Avoid using fully qualified class names (such as java.nio.file.attribute.FileTime) if there is no class name conflict in the file and the class is already imported, as it unnecessarily reduces code readability.

    Files.setLastModifiedTime(
        keyPath, FileTime.fromMillis(System.currentTimeMillis() + 10000));
References
  1. Do not use fully qualified class names if there is no class name conflict in the file and the class is already imported, as it unnecessarily reduces code readability.

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.

2 participants