Delegate tokenexchange HTTP plumbing to pkg/oauthproto#5226
Open
Delegate tokenexchange HTTP plumbing to pkg/oauthproto#5226
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5226 +/- ##
==========================================
- Coverage 67.87% 67.83% -0.04%
==========================================
Files 610 610
Lines 62383 62340 -43
==========================================
- Hits 42340 42287 -53
- Misses 16871 16874 +3
- Partials 3172 3179 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Now that pkg/oauthproto owns request construction, body draining, size
limiting, and RFC 6749 token-response parsing, tokenexchange no longer
needs its own copies. Route exchangeToken through
oauthproto.NewFormRequest + oauthproto.DoTokenRequest and drop the
in-package helpers plus the now-unused imports (encoding/json, io,
log/slog, strconv, time).
exchange.go changes:
- Delete createTokenExchangeRequest, executeTokenExchangeRequest,
parseTokenExchangeResponse, validateResponseStatus, the local
response struct, and the local maxResponseBodySize constant.
exchangeToken collapses to three calls: buildFormData,
oauthproto.NewFormRequest, oauthproto.DoTokenRequest.
- Preserve RFC 8693 Section 2.2.1 at the call site.
oauthproto.ParseTokenResponse is deliberately permissive about
token_type (x/oauth2 library parity, Google historically omits it),
so tokenSource.Token() still enforces both token_type and
issued_token_type for RFC 8693 compliance. The redundant
AccessToken-empty check is gone; oauthproto.ParseTokenResponse emits
"oauth: token response missing access_token (RFC 6749 Section 5.1)"
centrally.
- Rename buildTokenExchangeFormData to buildFormData now that the
package context makes the TokenExchange prefix redundant.
- The SubjectTokenType mutation (c.SubjectTokenType = normalized) is
preserved — pkg/vmcp and pkg/runner read the field post-Validate —
and pinned by a new TestExchangeConfig_Validate_NormalizesSubjectTokenType
regression test covering short forms, already-normalized URNs, and
empty.
Test changes:
- Replace the in-file testResponse struct and responseBuilder helper
with oauthtest.ResponseBuilder from pkg/oauthproto/oauthtest. The
shared builder was introduced in the first commit of this series
specifically so tokenexchange and the upcoming jwtbearer grant stop
drifting; keeping a local duplicate defeated the point. Same
treatment for three literal testResponse{...} constructions in
middleware_test.go.
- Update error-substring assertions to match the new emitters in
pkg/oauthproto: "token exchange request failed" -> "oauth: token
request failed", "failed to parse token exchange response" ->
"oauth: cannot parse token response", "failed to create token
exchange request" -> "tokenexchange: build request". In
pkg/vmcp/auth/strategies/tokenexchange_test.go, "empty access_token"
-> "missing access_token" because oauthproto.ParseTokenResponse now
owns that §5.1 check.
RetrieveError.Body preservation:
- pkg/oauthproto.parseRetrieveError leaves the raw upstream body on
*oauth2.RetrieveError so general-purpose callers can surface server
replies verbatim. tokenexchange historically scrubbed this field to
prevent raw IdP content from leaking into error strings (matching the
pattern used by Ory Hydra). exchangeToken now opts back into the
stricter behavior with an errors.As + Body = nil step right after
DoTokenRequest. The TestExchangeToken_HTTPErrorResponses table
re-asserts Body == nil so the property cannot drift again.
User-facing: operators or alerts keyed on the old tokenexchange
error strings will need to pick up the new substrings. Error types
are unchanged (*oauth2.RetrieveError still surfaces on non-2xx and
2xx-with-error bodies; errors.As chains still work)
96acd35 to
2e9af0c
Compare
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.
Summary
pkg/auth/tokenexchangecarried its own copies of token-endpoint request construction, body draining, size capping, and RFC 6749 response parsing. Earlier commits in this series moved those intopkg/oauthprotofor the upcoming JWT-bearer grant. Keeping the local duplicates around meant the two paths would drift, so this PR completes the migration and fulfils theTODO: drop when executeTokenExchangeRequest is replaced by oauthproto.DoTokenRequestplanted in Use shared pkg/oauthproto helpers in tokenexchange #5212.exchangeTokennow collapses tobuildFormData+oauthproto.NewFormRequest+oauthproto.DoTokenRequest. The 5 local helpers (createTokenExchangeRequest,executeTokenExchangeRequest,parseTokenExchangeResponse,validateResponseStatus, the localresponsestruct, and themaxResponseBodySizeconstant) are deleted, along with the now-unused importsencoding/json,io,log/slog,strconv,time.token_typeandissued_token_type) is preserved at the call site —oauthproto.ParseTokenResponseis intentionally permissive ontoken_typeto matchx/oauth2/Google, so the exchange grant tightens it back. The redundantaccess_tokenempty check is gone;oauthproto.ParseTokenResponseemits"oauth: token response missing access_token (RFC 6749 Section 5.1)"centrally.SubjectTokenTypeshort-form normalization inValidate()is preserved and pinned byTestExchangeConfig_Validate_NormalizesSubjectTokenType.responseBuilderto the sharedoauthtest.ResponseBuilderfrompkg/oauthproto/oauthtest. Three literalresponse{...}constructions inmiddleware_test.goget the same treatment.RetrieveError.Bodyscrubbing is preserved as an explicit opt-in:pkg/oauthproto.parseRetrieveErrorkeepsBodypopulated for general-purpose callers, buttokenexchange.exchangeTokendoes anerrors.As+Body = nilstep right afterDoTokenRequest.TestExchangeToken_HTTPErrorResponsesre-assertsBody == nilso the property cannot drift again.Type of change
Test plan
task test) —pkg/auth/tokenexchange/...andpkg/vmcp/auth/strategies/...pass with-race.task lint-fix) — 0 issues.task test-e2evia PR CI on kindest/node v1.33/v1.34/v1.35).API Compatibility
v1beta1API.Does this introduce a user-facing change?
Yes — operators or alerts keyed on the old
tokenexchangeerror strings need to pick up the new substrings:"token exchange request failed"→"oauth: token request failed""failed to parse token exchange response"→"oauth: cannot parse token response""failed to create token exchange request"→"tokenexchange: build request""empty access_token"(vmcp strategy path) →"missing access_token"Error types are unchanged:
*oauth2.RetrieveErrorstill surfaces on non-2xx responses (and now on 2xx responses that carry an RFC 6749 §5.2errorfield), anderrors.Aschains still work.Special notes for reviewers
pkg/oauthprotoconsolidation series (after Use shared pkg/oauthproto helpers in tokenexchange #5212 and the CIMD refactor). The sharedNewFormRequest/DoTokenRequest/ParseTokenResponsehelpers landed in earlier commits; this PR is purely the consumer migration.RetrieveError(commit body section "RetrieveError.Body preservation") is an explicit opt-in to keep the pre-refactor security property —pkg/oauthprotodeliberately preservesBodyso general-purpose callers can surface server replies verbatim, but thetokenexchangeerrors flow through paths that may logerr.Error(), so the package opts back into the stricter behavior.exchange_test.gois migrated tooauthtest.ResponseBuilder); the production-code delta inexchange.gois the substantive change.ExchangeConfig.Validate()onTokenURLto reject plain-HTTP non-localhost endpoints. That hardening was reverted (force-pushed) after the e2e suite — which spins up cluster-local plain-HTTP mock auth servers — surfaced it as a behavior change. A refactor PR shouldn't carry it; the tightening will be proposed separately with appropriate plumbing for in-cluster test setups.🤖 Generated with Claude Code