Repository navigation
Commit 1de87fd
authored
feat(auth): add DISABLE_BOUND_ID_TOKEN option to opt out of certificate-bound ID tokens (#14559)
> [!IMPORTANT]
> **This must merge before the `agentic-identities-bound-token` feature
branch is merged to `main`.** Docs PR #14557 documents this option.
## Why
When an agent identity certificate is available,
`ComputeEngineCredentials` requests certificate-bound ID tokens by
default. A bound ID token is only accepted by targets that authenticate
the caller over mTLS with the same certificate. Targets reached over
standard HTTPS, such as a Cloud Run service's `*.run.app` URL or a
custom domain, reject it with `401 Unauthorized`.
Per the [ID token binding
1-pager](https://docs.google.com/document/d/1yL_xdDimlUlUyid6HDtqZUIMQjX9VznwzXn1SBABjZc/edit?pli=1&resourcekey=0-GxSXGdfH5Ni_nSI9f7xkQg&tab=t.0#heading=h.94fnt3dk2gx)
(Sep 30 decision), ID tokens stay bound by default, and callers get a
per-target `bind_id_token` setting to turn binding off where needed. The
process-wide opt-out (`GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false`) is
unchanged.
## What changes
- New `IdTokenProvider.Option.DISABLE_BOUND_ID_TOKEN`. Callers pass it
per target, the same way as `FORMAT_FULL` and `LICENSES_TRUE`:
```java
IdTokenCredentials.newBuilder()
.setIdTokenProvider((IdTokenProvider) credentials)
.setTargetAudience("https://my-service-12345.us-central1.run.app")
.setOptions(Arrays.asList(IdTokenProvider.Option.DISABLE_BOUND_ID_TOKEN))
.build();
```
- `ComputeEngineCredentials.idTokenWithAudience()`: with the option, the
certificate lookup is skipped and the identity endpoint is called with a
plain GET. Without it, behavior is unchanged (bound whenever a
certificate is available and binding isn't turned off by the env var).
- Other credential types (`ServiceAccountCredentials`,
`ImpersonatedCredentials`, `UserCredentials`) already ignore options
they don't use, so the same code works outside agent identity
environments.
- Access tokens are unaffected.
### API shape
`Option` is a set of on/off flags rather than a value, so this adds only
the "off" flag (`DISABLE_BOUND_ID_TOKEN`). Leaving it unset means
"auto", which currently means bound. This matches the 1-pager's
tri-state (unset = auto) without adding an "enable" flag that would do
nothing today; one can be added later without breaking callers if "auto"
starts deciding per target. Python exposes the same control as
`bind_id_token: Optional[bool]` (googleapis/google-cloud-python#18559).
## Tests
### Unit tests
New `ComputeEngineCredentialsTest` cases, all with an agent identity
certificate present and binding enabled:
- `idTokenWithAudience_disableBoundIdToken_requestsUnboundToken`: GET,
no request body, no forced `format=full`.
-
`idTokenWithAudience_disableBoundIdTokenWithFormatFull_requestsUnboundFullToken`:
still GET; `format=full` is kept when the caller asks for it.
- `idTokenWithAudience_disableBoundIdToken_skipsCertificateLookup`: with
a certificate config pointing to a missing file, the default request
fails, but the opted-out request succeeds, which shows the certificate
is never read.
-
`idTokenCredentials_withDisableBoundIdTokenOption_requestsUnboundToken`:
the option set on `IdTokenCredentials` reaches
`ComputeEngineCredentials`.
The existing bound-ID-token tests are unchanged and still pass. Full
`oauth2_http` suite: 1064 tests, 0 failures. `fmt` is clean.
### Live test on GKE
Run on a GKE cluster with agent identity enabled, using a test-only
local merge of this PR with #14595 (GKE support) and #14556 (already on
the base branch). A single process fetches ID tokens through
`IdTokenCredentials` from `ComputeEngineCredentials` (ADC) and sends
them to an agent-to-agent receiver pod. The receiver accepts only bound
tokens over mTLS (whose `cnf` must match the client certificate), and
only unbound tokens over plain HTTP.
| Check | Defaults | `GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN=false` |
|---|---|---|
| ID token with `DISABLE_BOUND_ID_TOKEN` | **unbound** (no `cnf`);
receiver: plain HTTP 200, mTLS 401 | unbound |
| ID token for another target without the option, same credentials,
fetched afterwards | **still bound** (`cnf` = certificate leaf);
receiver: mTLS 200 | unbound |
| Access token after the ID-token opt-out | **still bound** (401 on a
plain, non-mTLS endpoint) | unbound |
The option unbinds only the ID token it's set on. Other targets and
access tokens are unaffected, and it's harmless when binding is already
off.
**Internal links (Googlers only):**
[results](https://paste.googleplex.com/5926225961418752) ·
[harness](https://paste.googleplex.com/5713819712749568) · cluster
[setup](https://paste.googleplex.com/5635555912712192) and
[manifests](https://paste.googleplex.com/6258309762514944) (shared with
#14595)1 parent 03af200 commit 1de87fd
3 files changed
Lines changed: 122 additions & 2 deletions
File tree
- google-auth-library-java/oauth2_http
- javatests/com/google/auth/oauth2
- java/com/google/auth/oauth2
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
490 | 490 | | |
491 | 491 | | |
492 | 492 | | |
| 493 | + | |
| 494 | + | |
493 | 495 | | |
494 | 496 | | |
495 | 497 | | |
496 | 498 | | |
497 | 499 | | |
498 | 500 | | |
499 | 501 | | |
500 | | - | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
501 | 506 | | |
502 | 507 | | |
503 | 508 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| |||
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
63 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
64 | 86 | | |
65 | 87 | | |
66 | 88 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1386 | 1386 | | |
1387 | 1387 | | |
1388 | 1388 | | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
1389 | 1482 | | |
1390 | 1483 | | |
1391 | 1484 | | |
| |||
0 commit comments