Repository navigation
GCP OIDC: direct resource access fails with "GCP STS token expires too soon" #262
Description
Summary
When a Google Artifact Registry registry is configured for OIDC w/ direct resource access (ie no service account impersonation), the token exchange fails because the token lifetime is too short.
Here's some mildly-redacted logs from a Dependabot run on a repo of mine (I've substituted https://example.com/simple/ and pandas instead of my actual registry URL and package name)
updater | 2026/09/22 19:39:27 INFO <job_1587786315> Fetching release information from simple registry at https://example.com/simple/ for pandas
proxy | 2026/09/22 19:39:28 [018] GET https://example.com:443/simple/pandas/
proxy | 2026/09/22 19:39:28 [018] * failed to get gcp token via OIDC for example.com: failed to get gcp access token: failed to exchange GitHub token for GCP token: GCP STS token expires too soon (4m58s remaining)
proxy | 2026/09/22 19:39:28 [018] 401 https://example.com:443/simple/pandas/
2026/09/22 19:39:28 [018] Remote response: The request does not have valid authentication credentials.
proxy | 2026/09/22 19:39:28 [020] GET https://example.com:443/simple/
2026/09/22 19:39:28 [020] * failed to get gcp token via OIDC for example.com: credential has been rejected due to previous authentication failure
proxy | 2026/09/22 19:39:28 [020] 404 https://example.com:443/simple/
And then every subsequent request to the registry is rejected with the same "credential has been rejected due to previous authentication failure".
Prospective cause
The direct-WIF branch rejects any STS token with 5 minutes or less remaining:
proxy/internal/oidc/actions_oidc.go
Lines 758 to 763 in 8581295
| // If no service account, return the federated token directly (direct WIF) | |
| if params.ServiceAccount == "" { | |
| stsExpiry := time.Duration(stsTokenResp.ExpiresIn) * time.Second | |
| if stsExpiry <= 5*time.Minute { | |
| return nil, fmt.Errorf("GCP STS token expires too soon (%v remaining)", stsExpiry) | |
| } |
And I don't think that check can ever pass, because:
- "Workload Identity Federation sets the [STS] token expiry so that it matches the expiry of the external token." (from Google's docs: Token types > Federated Access Tokens).
- The external token is the GitHub Actions OIDC token, which (apparently) lasts 5 minutes and can't be configured. Evidence is a little more circumspect here:
- a Google action maintainer saying that the duration became 5 min (link)
- a Google action's docs saying the duration is 5 min (link)
- a GitHub docs page with an OIDC token example where the
iat/exp(issued at/expires at) values are 300s/5m apart (link) - and people asking GitHub for ways to make the token live longer (link)
I think the 5m threshold was added as part of the initial integration with GAR in #108, maybe as part of an attempt to cache the token for a bit? (There was an automated review comment about it.)
Configuration
dependabot.yml (redacted):
registries:
gar-python:
type: python-index
url: https://example.com/simple
workload-identity-provider: projects/<project-number>/locations/global/workloadIdentityPools/<pool>/providers/<provider>In GCP, the WIF provider trusts https://token.actions.githubusercontent.com, and the WIF pool principal set is granted roles/artifactregistry.reader on the repository. The STS exchange itself seems to succeed, but the proxy discards the result.
GitHub-hosted runner (ubuntu-24.04, image 20260920.314),
uv ecosystem, 2026-09-22
Expected
Direct WIF should work, since the docs list service-account as optional for Google Artifact Registry (Configuring access to private registries > Google Cloud Artifact Registry).
Note that Google also recommends direct resource access over service account impersonation where possible (link - pretty explicit, link - describes service accounts as an "alternative")
Possible fixes?
- Accept short-lived STS tokens on the direct path, and let the cache refresh them with a buffer scaled to the token's lifetime rather than a fixed 5 min? (Or maybe just re-exchange if minting a fresh GitHub token is cheap?)
- Or if direct WIF isn't meant to be supported, document that
service-accountis effectively required, and reject configs without it with a clear error.
Activity
ci(dependabot): fetch the design system from Artifact Registry (#172)