fix(coreapi): handle cross-jurisdiction routing for control-plane commands - #1457
Conversation
…mands Control-plane commands (mirror create/delete/collaborators, repo, grant, …) pinned to the home core, sent the bare login JWT, and had no 421 follow or token exchange — so any operation on a resource whose home jurisdiction is another region failed: the home core returns 421, which surfaced as a generic decode error, and even reaching the foreign core a home-region login JWT can't verify there (local-only JWKS → bare 401). Wrap the coreapi client's transport with a cross-juris RoundTripper: - follows a 421's home_core_url (gated by the responding core's /.well-known/entire-federation manifest); - after the redirect, a bare 401 (the home core can't verify a foreign-region JWT's signature, so it emits no cross_juris hint) triggers a proactive RFC 8693 exchange at <home>/oauth/token, where the target core federates the sibling JWKS and mints a token it accepts; - also handles the structured cross_juris_token_required hint; - per-origin token cache, HTTPS/same-origin/federation trust gates. Reuses the existing httputil.PostOAuthToken exchange primitive.
There was a problem hiding this comment.
Pull request overview
This PR updates the internal/coreapi (control-plane) HTTP client to support cross-jurisdiction routing by automatically following 421 home_core_url responses (with federation trust gating) and performing RFC 8693 token exchange when required, so control-plane commands work across regions.
Changes:
- Added a cross-jurisdiction
http.RoundTripperthat follows 421 redirects (validated via/.well-known/entire-federation) and performs token exchange on structured 401 hints or bare 401s after a 421. - Wired the generated Core API client to use the new cross-juris HTTP client via
WithClient(...)in bothNew()andNewWithBearer(...). - Added unit tests covering redirect following, exchange behavior, and trust-gating rules.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| internal/coreapi/cross_juris_transport.go | Implements the cross-jurisdiction transport (421 follow + 401-triggered token exchange) and per-origin caching/trust gates. |
| internal/coreapi/cross_juris_transport_test.go | Adds tests for redirect/exchange flows and security gates (federation + same-origin validation). |
| internal/coreapi/client.go | Wraps the generated Core API client with the new cross-jurisdiction HTTP client. |
NewWithBearer's doc claimed "no per-request resolution or STS exchange", which the cross-juris transport now contradicts (it exchanges on a 421). Update New/NewWithBearer to note the 421-follow + token exchange, and drop the bare core/api/middleware.go / core/coreapi/mirrors.go path references (entire-core server files, not present in this repo) in favor of naming entire-core directly.
- goconst: extract the repeated "/oauth/token" literal to a const (nolint G101 — it's a URL path, not a credential). - gosec G704: annotate the federation fetch; origin is a core the CLI already dials and the path is a fixed well-known endpoint, not SSRF. - -race: capture Authorization headers recorded in httptest handler goroutines through a mutex-guarded recorder (HTTP completion isn't a happens-before edge; matches the channel pattern in client_test.go).
|
Ooh hangon i have concerns about this, i'll make it DRAFT again. |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6a23ba4. Configure here.
All tests own their httptest servers/transports with no process-global state, so they conform to the repo's t.Parallel() convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 41b4654948eb
Match the entiredb staging bed's transport one-for-one: - drainAndRestoreBody uses io.LimitReader and restores resp.Body even on read error (drops the nil-ResponseWriter MaxBytesReader idiom). - port the four tests the original CLI port dropped: TokenCacheReuses- Exchanged, NoInfiniteLoopOn421Chain, ExchangeFailurePropagates401, and ExchangeAfter401Then421UsesOriginalSubjectToken (the chained-exchange subject_token regression). goconst/intrange satisfied (hoisted Bearer literals, range-over-int).
exchangeSubjectToken strips oauthTokenPath and PostOAuthToken re-appends it, but validateExchangeURL only gated scheme and host. A hint whose path wasn't /oauth/token would make the strip a no-op and POST to a server-chosen path. Validate the path so the round-trip is honest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: ea9e8dae6384
storeToken always used the fixed 4m cachedTokenTTL and discarded the expires_in PostOAuthToken returns. A server token shorter than 4m could be served stale, producing a 401 the cache assumed impossible. Thread expires_in through and cache for min(expires_in - buffer, cachedTokenTTL), declining to cache lifetimes shorter than the buffer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1edf9ab1ef2c
The struct doc claimed the cache TTL "matches the foreign-session token's lifetime (5 minutes)" while cachedTokenTTL is 4m. Describe the actual expires_in-honoring, buffered behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: c6abc0aac791

https://entire.io/gh/entireio/cli/trails/599
What
Control-plane commands (
mirror create/delete/collaborators,repo,grant, …) didn't work cross-region. They pinned to the home core, sent the bare login JWT, and had no 421-follow or token exchange. Operating on a resource whose home jurisdiction is another region failed: the home core's 421 surfaced as a generic decode error, and even reaching the foreign core a home-region login JWT can't verify there (local-only JWKS → bareinvalid token401, with nocross_juris_token_requiredhint).Fix
Wrap the
coreapiclient's transport with a cross-jurisRoundTripper(WithClient):home_core_url, gated by the responding core's/.well-known/entire-federationmanifest.<home>/oauth/token— the home core can't verify a foreign-region JWT's signature so it never emits the hint, but its/oauth/tokenfederates the sibling JWKS and mints a token it accepts.cross_juris_token_requiredhint.Reuses the existing
httputil.PostOAuthTokenprimitive. Inert for same-jurisdiction calls.Test
Run a control-plane command against a resource in another region (e.g.
entire mirror collaborators ...for a US-jurisdiction cluster as an EU user) — it now follows the 421 and exchanges automatically instead of erroring.Mirrors the server-side + entire-core-CLI fix in entirehq/entiredb (no server change needed; the 421
home_core_url, federation manifest, and sibling JWKS federation are already in place).🤖 Generated with Claude Code
Note
High Risk
Changes authentication transport for all control-plane API calls: redirects, token exchange, and federation trust directly affect whether login JWTs are sent and where; mistakes could break cross-region ops or weaken JWT exfiltration protections.
Overview
Control-plane coreapi clients now use a cross-jurisdiction HTTP transport so mirror/repo/grant-style commands work when a resource’s home region differs from the user’s login region.
The transport follows 421 responses using
home_core_url, but only if the redirect host appears in the responding core’s/.well-known/entire-federationpeer list. After redirect, it handlescross_juris_token_required401s and the bare 401 case (foreign login JWT fails local JWKS) by RFC 8693 token exchange at same-origin/oauth/token, always using the original login JWT assubject_token, caching exchanged tokens per origin (~4m TTL), and retrying with buffered request bodies.Trust gates block off-origin exchange URLs and non-HTTPS targets (except loopback for tests) so server-supplied URLs cannot exfiltrate the login JWT. Same-jurisdiction traffic is unchanged.
New()andNewWithBearer()both opt in viaWithClient(newCrossJurisHTTPClient()); tests cover federation rejection, off-origin hints, 421→exchange, and body replay.Reviewed by Cursor Bugbot for commit 6a23ba4. Configure here.