Skip to content

git-remote-entire: migrate the ENTIRE_TOKEN (CI) path to jurisdiction tokens - #1622

Merged
Soph merged 1 commit into
mainfrom
paul/cor-847-env-token-identity
Jul 4, 2026
Merged

Soph merged 1 commit into
mainfrom
paul/cor-847-env-token-identity

Conversation

@toothbrush

@toothbrush toothbrush commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1621. Fixes COR-847.

Why

The ENTIRE_TOKEN (CI / Buildkite) path was the last client of the repo-scoped exchange, paying it per (repo, action) on every git command — that's also happening in CI.

What

  • resolveEnvTokenCreds now mints a jurisdiction access token instead of building a repocreds.Cache: audience = the cluster's advertised jurisdiction_audience, exchanged at the core derived from the token's aud claim (unchanged trust gate against the cluster's advertised core set). A cluster advertising no jurisdiction_audience fails closed with the same upgrade hint as the interactive path (shared missingJurisdictionAudienceErr).
  • The env flavour pins the exchange core (sa-session JWTs carry no home_jurisdiction claim to route by) and never touches the OS keychain — in-process memo only; Invalidate() (the 401 observer) drops just the memo.
  • Both paths now return *jurisdictionTokenSource, so the tokenSource interface, the repocreds dependency in git-remote-entire, and the unused clusterBaseURL plumbing are gone. Token() drops its ignored (repo, action) params; the smart-HTTP endpoint classification stays as the credential-attachment gate.
  • clusterdiscovery.ResolveClusterCores returns the full discovery entry and adopts the audience-requiring cache semantics (pre-audience entries refetched, no audience-less stale fallback) — its sole caller is now audience-requiring.

Verified server-side that the exchange gates hold for CI subjects: sa-session tokens carry LoginScopes ⊇ entire:session (passes validateIdentityExchange), have aud == iss, and are explicitly carved out of the fid-liveness gate. No entiredb change needed.

Follow-up

  • Sunset validateRepoExchange server-side once nothing mints repo-scoped tokens (needs a released helper + bumped Buildkite plugin pin).

Tests

  • go test ./... green; new tests: env source pins the token's core, mints in-memory only (no token-store writes, memo-only Invalidate), missing jurisdiction_audience fails closed.

🤖 Generated with Claude Code


Note

Medium Risk
Changes authentication for all CI git operations and removes the repo-scoped fallback; clusters without jurisdiction_audience will fail until upgraded, but the existing ENTIRE_TOKEN trust gate and fail-closed behavior are preserved.

Overview
CI / ENTIRE_TOKEN auth now uses jurisdiction access tokens instead of per-(repo, action) repo-scoped exchanges, so Buildkite-style runners pay one exchange per process (memoized) rather than on every git command.

resolveEnvTokenCreds builds newEnvJurisdictionTokenSource: exchange at the trust-gated core from the token’s aud, audience from cluster jurisdiction_audience, pinned core (no home_jurisdiction on sa-session JWTs), and no OS keychain—only in-process cache; Invalidate on 401 clears the memo only. Missing jurisdiction_audience fails closed via shared missingJurisdictionAudienceErr (no repo-scoped fallback).

Both interactive and env paths return *jurisdictionTokenSource; repocreds and tokenSource are removed, Token(ctx) no longer takes repo/action (smart-HTTP classification still gates where Bearer is attached). ResolveClusterCores returns the full discovery entry and uses audience-required cache semantics for the env path.

Tests cover pinned core routing, in-memory-only env minting, and missing-audience rejection.

Reviewed by Cursor Bugbot for commit 17b1dcd. Configure here.

@toothbrush
toothbrush requested a review from a team as a code owner July 3, 2026 03:22
Copilot AI review requested due to automatic review settings July 3, 2026 03:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the ENTIRE_TOKEN (CI / workload identity) authentication path in git-remote-entire from per-(repo,action) repo-scoped token exchange to jurisdiction identity tokens, aligning it with the interactive auth model and removing the hot-path exchange cost in CI.

Changes:

  • Switch the env-token (ENTIRE_TOKEN) flow to mint jurisdiction identity tokens (in-process memo only; no keychain).
  • Simplify the credential plumbing by removing the tokenSource abstraction and dropping unused repo-scoped parameters.
  • Extend cluster discovery to return the full discovery entry (cores + jurisdiction audience/core) for the env-token trust gate.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/entireclient/clusterdiscovery/resolve.go Updates ResolveClusterCores to return the full discovery entry needed by the env-token path.
cmd/git-remote-entire/main.go Migrates env-token auth to identity tokens, removes repo-scoped plumbing, and adjusts credential attachment gating.
cmd/git-remote-entire/main_test.go Updates the well-known stub and adds tests validating env-token identity token behavior and fail-closed upgrade hint.
cmd/git-remote-entire/identityauth.go Adds the env-token identity-token source variant (pinned core, no persistence) and updates token acquisition API.
cmd/git-remote-entire/identityauth_test.go Updates call sites for the new Token(ctx) signature and adds coverage for the env pinned-core path and non-persistence.

Comment thread cmd/git-remote-entire/main.go Outdated
Comment thread cmd/git-remote-entire/main.go Outdated
Comment thread cmd/git-remote-entire/main.go Outdated
@toothbrush toothbrush changed the title git-remote-entire: migrate the ENTIRE_TOKEN (CI) path to identity tokens git-remote-entire: migrate the ENTIRE_TOKEN (CI) path to jurisdiction tokens Jul 3, 2026
@toothbrush

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 17b1dcd. Configure here.

@toothbrush
toothbrush force-pushed the paul/cor-847-env-token-identity branch from 17b1dcd to ebab5cf Compare July 3, 2026 05:10
Base automatically changed from jwt-latency-experiment to main July 3, 2026 05:17
… tokens

The env-token path was the last client of the repo-scoped exchange,
paying it per (repo, action) on every git command. It now mints one
jurisdiction token at the token's own trust-gated core, using the
cluster's advertised jurisdiction_audience — in-memory only, no keychain
on CI runners; Invalidate (the 401 observer) drops just the memo
(COR-847).

Because clusters advertise every jurisdiction's cores, a token minted at
a sibling core passes the trust gate but its exchange is refused with an
opaque invalid_target. Pre-compute the actionable hint at resolve time —
'point your CI auth url at <jurisdiction core>' — and append it to the
exchange failure. Hint, not a pre-flight error: clusters may advertise
several same-jurisdiction core URLs, so URL inequality is a strong
signal, not proof.

With repocreds gone from this binary, the tokenSource seam collapses:
both paths return *jurisdictionTokenSource and Token() drops its ignored
(repo, action) params. ResolveClusterCores returns the full discovery
entry and adopts the audience-requiring cache semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@toothbrush
toothbrush force-pushed the paul/cor-847-env-token-identity branch from ebab5cf to f45fcb7 Compare July 4, 2026 02:13
@Soph
Soph merged commit 1b67f2c into main Jul 4, 2026
10 checks passed
@Soph
Soph deleted the paul/cor-847-env-token-identity branch July 4, 2026 08:38
timothybrush pushed a commit to timothybrush/cli-2 that referenced this pull request Sep 15, 2026
fatalMessage special-cased an RFC 8693 invalid_target error whose
description named the cluster actually serving the repo, turning it into
"this repository is not hosted on X; it lives on Y" plus a corrected
clone command. Nothing can produce that error any more, so the branch has
never been reachable in main.

It was added in entireio#1575 (2026-06-30), built on the repo-scoped token
exchange the helper performed at the time. entireio#1621 removed that exchange
for the interactive path — merged 2026-07-03 05:17, six hours BEFORE
entireio#1575 merged at 11:22 — and entireio#1622 migrated the ENTIRE_TOKEN path the next
morning. Both PRs were the same author's; neither conflicted with entireio#1575
in any file, so nothing flagged it. The helper now presents a
jurisdiction token as a bearer and never exchanges, so no invalid_target
is ever returned to it.

Measured rather than reasoned. Cloning a single-placement repo from the
wrong cluster, against a helper built from main:

    $ git clone entire://aws-eu-central-1.entire.io/gh/entireio/cli-perf-benchmarks
    fatal: stateless-connect v2 info/refs: Repository not found

with ENTIRE_DEBUG showing the wire:

    GET /gh/entireio/cli-perf-benchmarks/info/refs?service=git-upload-pack
    HTTP/1.1 404 Not Found
    Repository not found

A plain 404 with a four-word body: no invalid_target, no
error_description, nothing naming the correct host. The same repo on its
own cluster clones fine with the same credentials, so that 404 is the
wrong-cluster response rather than an access failure.

So this is not repairable here. The 404 carries no information about
where the repo lives, and no client-side matcher can invent it. Restoring
the UX needs the data plane to say so on that response — worth noting the
helper already looks for an X-Entire-Replicas header there and logs its
absence, so the mechanism may partly exist. Filed separately; this commit
only removes code that cannot run.

TestFatalMessage went with it. It passed throughout by constructing the
invalid_target error itself, which verified the formatting and could
never observe that the input had no producer — the reason a dead branch
survived two years of green CI.

fatalMessage reduced to a single Sprintf with one caller, so it is
inlined rather than kept as a seam. If the hint returns it will match a
404 body, not an OAuth error, and would not reuse this shape.

Verified: identical stderr before and after (the point of the change),
lint clean, GOOS=windows vet clean, helper and remotehelper suites pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2863QYHMEQG3P93TAGG0FGW
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants