Add GCP OIDC token exchange for Google Artifact Registry#108
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Google Artifact Registry as an additional OIDC provider in the Dependabot MITM proxy, enabling short-lived GCP access tokens to be minted from GitHub Actions OIDC and injected on outbound registry requests (including Docker’s oauth2accesstoken:<token> Basic auth requirement).
Changes:
- Introduces
GCPOIDCParametersand wires GCP into OIDC credential creation and token refresh flow. - Implements GCP token exchange via Google STS, with optional IAM Credentials
generateAccessTokenimpersonation. - Extends OIDC request auth injection logic and adds/updates tests to cover Bearer vs Docker Basic auth behavior.
Show a summary per file
| File | Description |
|---|---|
| internal/oidc/actions_oidc.go | Adds GCP STS token exchange + optional IAM impersonation functions and DevOps wrapper. |
| internal/oidc/actions_oidc_test.go | Adds unit tests for GCP STS/IAM flows and error handling. |
| internal/oidc/oidc_credential.go | Adds GCPOIDCParameters and integrates GCP into CreateOIDCCredential + token refresh switch. |
| internal/oidc/oidc_credential_test.go | Adds coverage for GCP parameter parsing/default audience behavior. |
| internal/oidc/oidc_registry.go | Adds GCP-specific auth injection (Bearer vs Docker Basic auth). |
| internal/oidc/oidc_registry_test.go | Adds registry-level tests verifying GCP Bearer and Docker Basic auth injection. |
| internal/handlers/oidc_handling_test.go | Adds end-to-end handler test cases ensuring GCP OIDC auth is applied across multiple registries/handlers. |
Copilot's findings
- Files reviewed: 7/7 changed files
- Comments generated: 1
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
internal/oidc/actions_oidc.go:802
- The IAM token expiry check uses
remaining < 5*time.Minute, but the OIDC token cache also subtracts 5 minutes fromExpiresInwhen settingtokenExpiry. Ifremainingis exactly 5 minutes, the cached token will be considered expired immediately and trigger a refresh loop. Consider using<= 5*time.Minutehere (or increasing the minimum remaining time) to ensureExpiresInstays positive after the cache’s 5-minute buffer.
remaining := time.Until(expireTime)
if remaining < 5*time.Minute {
return nil, fmt.Errorf("GCP IAM token expires too soon (%v remaining, service-account: %s)", remaining, params.ServiceAccount)
}
- Files reviewed: 7/7 changed files
- Comments generated: 2
Follow-up: Commonize OIDC handler test casesWhile working on this PR, I noticed that This could be collapsed with a table-driven approach: define each ecosystem's metadata once (handler factory, credential type, URL key, log label, URLs), then generate provider variants in a loop — reducing to ~13 ecosystem entries + a shared template (~75% less test code, same coverage). Suggesting this as a follow-up refactor to keep this PR focused. |
Follow-up: Extract shared
|
Follow-up: Extract JSON POST helpers for OIDC token exchangeAll OIDC providers (Azure, JFrog, AWS, Cloudsmith, GCP) repeat the same ~15-line HTTP boilerplate: marshal → create request → set standard headers → execute → read body. GCP does it twice (STS + IAM). Proposed approach: build/execute splitRather than a single helper with growing parameter lists, split into two: // buildJSONRequest creates a JSON POST request with standard proxy headers.
// Caller can modify the request (add headers, etc.) before executing it.
func buildJSONRequest(ctx context.Context, url string, body any) (*http.Request, error) {
bodyJSON, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyJSON))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "dependabot-proxy/1.0")
return req, nil
}
// executeJSONRequest executes a request and returns the status code and raw body.
func executeJSONRequest(req *http.Request) (int, []byte, error) {
client := &http.Client{Timeout: 10 * time.Second}
// ... execute, read body, return statusCode, body, err
}Usage example (GCP IAM with custom auth header):req, err := buildJSONRequest(ctx, iamURL, iamReqBody)
if err != nil { ... }
req.Header.Set("Authorization", "Bearer "+stsToken) // custom header
status, body, err := executeJSONRequest(req)
if err != nil { ... }Why this design:
|
Follow-up: Consider provider interface to replace type switchesAdding a new OIDC provider currently requires synchronized edits in 3 places:
An |
Follow-up: Simplify
|
0d80e93 to
5a60283
Compare
5a60283 to
9caf5c0
Compare
Add Google Artifact Registry as the fifth OIDC provider in the Dependabot proxy, alongside Azure DevOps, AWS CodeArtifact, JFrog, and Cloudsmith. Token exchange flow: - GitHub Actions OIDC JWT → Google STS token exchange - Optional IAM Credentials generateAccessToken impersonation (when service-account is configured) - Direct Workload Identity Federation when no service-account is present Auth injection: - Authorization: Bearer for most registry types (Maven, npm, Python, etc.) - Basic oauth2accesstoken:<token> for Docker hosts (*-docker.pkg.dev) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
9caf5c0 to
9a38819
Compare
What are you trying to accomplish?
Add Google Artifact Registry as the fifth OIDC provider in the Dependabot proxy, alongside Azure DevOps, AWS CodeArtifact, JFrog, and Cloudsmith. The proxy mints a short-lived Google access token from the GitHub Actions OIDC JWT and injects it on outbound registry requests — no static credentials stored.
Token exchange flow:
generateAccessTokenimpersonation (whenservice-accountis configured)service-accountis presentAuth injection:
Authorization: Bearer <token>for most registry types (Maven, npm, Python, etc.)Basic oauth2accesstoken:<token>for Docker hosts (*-docker.pkg.dev)Anything you want to highlight for special attention from reviewers?
oauth2accesstoken:<token>) is new — all 4 existing providers use Bearer or X-Api-Key. GCP Artifact Registry's Docker API requires this specific format.net/httpmatching existing providers.How will you know you have accomplished your goal?
go test ./... -count=1— all packages passgo vet ./...— cleango build ./...— succeedsChecklist