Add signjwt credential mode for auto-managed direct Cloud Run IAP - #5
Merged
Conversation
…ect Cloud Run IAP
… enable-API step, drop redundant field, soften OIDC framing
knwoop
marked this pull request as ready for review
July 27, 2026 12:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Local MCP clients can now reach Cloud Run services whose IAP was enabled the modern default way (
gcloud beta run services update --iap, auto-managed OAuth client). Live testing showed that setup rejects every Google-issued OIDC ID token the proxy could previously mint — so all existing modes (adc/impersonate/oauth) fail against it withInvalid IAP credentials: Invalid JWT audience, regardless of audience format. The new--credentials=signjwtmode signs a self-signed service-account JWT via the IAM CredentialssignJwtAPI, which such IAP accepts, unblocking the full MCP handshake end to end.The four OIDC modes are unchanged and still documented for custom-OAuth-client / LB-backed IAP.
Why
The IAP OAuth Admin API was shut down (March 2026), so new IAP deployments are forced onto the auto-managed client. Google's documented programmatic-access path for it is a self-signed SA JWT, not an ID token — a token type the proxy simply couldn't produce, making it unusable against any freshly-created direct Cloud Run IAP. This was found by testing against a real IAP-protected Cloud Run service: same audience, OIDC rejected but a hand-signed SA JWT accepted, so the blocker is the token type, not the audience.
Notes for reviewers
signjwtreuses--impersonate-service-accountas the target SA (signed-as, via ADC as base identity). Caller needsroles/iam.serviceAccountTokenCreatoron the SA; the SA needsroles/iap.httpsResourceAccessoron the IAP resource.signjwtdefaults to the upstream origin plus/*(the canonicalrun.appURL; origin-only and the project-number URL are rejected), other modes keep origin-only. Overridable with--audience.expclaim, so the existingCached/tokenExpiryrefresh path works unchanged (covered by a test).Verification
go test -race ./...,go vet ./...,golangci-lint run— all pass. New unit tests: signjwt claim construction, exp-parses-in-cache, and mode-aware audience defaulting.signjwtwithout an SA errors cleanly (exit 2); audience defaulting logshttps://svc-xxxx-an.a.run.app/*.initialize/tools/list/tools/callsucceed through IAP with a self-signed SA JWT — and the claim shape and audience default here match exactly what worked.🤖 Generated with Claude Code