feat(proxy): inject GitHub authorization at the MITM proxy so agents never hold usable tokens (#1861) - #4032
feat(proxy): inject GitHub authorization at the MITM proxy so agents never hold usable tokens (#1861)#4032clubanderson wants to merge 1 commit into
Conversation
…never hold usable tokens (#1861) Opt-in via HIVE_PROXY_INJECT_GH_AUTH (default OFF, byte-identical behavior when unset). When enabled: - WriteAgentToken diverts the real tier-scoped token to an in-memory registry the proxy injects from, and writes the visibly-fake placeholder hive-proxy-injected-<agent> to the agent-readable cache (the single choke point gh-wrapper.sh, git-credential-hive.sh, and the manager's GITHUB_TOKEN env push all read). - The MITM proxy strips any agent-supplied Authorization header and injects the UID-identified agent's hub-held scoped token: token scheme for REST/GraphQL, Basic x-access-token for git smart HTTP (with Connection: close so keep-alive reuse cannot bypass the rewrite via the raw git relay). - MITM widens to every GitHub-family host (github.com, registered GHE) under the flag, because an opaque tunnel would carry the placeholder to GitHub un-replaced. - Unknown agent = no injection, no fallback: the request proceeds unauthenticated and fails loud at GitHub. The hive's own control-plane calls (internalCallerName, UID-attributed) pass through untouched. - OAuth device-flow endpoints get strip-only treatment. - Logs carry agent name + injected yes/no, never token bytes. Part of #1861 Signed-off-by: Andy Anderson <andy@clubanderson.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
On hold — do not merge. This changes the GitHub credential path for every agent (behind a default-OFF flag), and the operator wants it reviewed deliberately before it lands even dormant. Leaving the branch and CI as-is; no auto-merge watcher is armed on it. |
|
PR needs rebase. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
bin/agent-env-scrub.sh claimed same-uid /proc environ extraction was already closed by the proxy Authorization strip/inject lane from #1861/#4032. That mitigation is still parked in PR #4032, so document the residual as open until that work lands and note that smuggled credentials remain usable against GitHub today. Signed-off-by: Andrew Anderson <andy@clubanderson.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Superseded by #5876 — this feature targets the v5 line, and the port (with v5 adaptations: NeedsInspection composition, proxyHTTP capabilities param, atomic tunnelHalfCloseDrain, hivecommons module path) is now up there. Closing this v4-based PR. |
What this ships
The core of #1861, item 1 as re-scoped by triage: GitHub authorization is injected at the MITM proxy, per identified agent, so that with the flag on, nothing an agent holds authenticates anywhere. Opt-in via
HIVE_PROXY_INJECT_GH_AUTH=true, default OFF — with the flag unset, token delivery and proxy behavior are byte-identical to today. The fleet keeps working while this soaks; do not flip the default here.Both prerequisites the triage recorded are honored:
identifyAgentFromConn's UID-map resolution. The self-asserted fallback this issue's triage flagged as Blocker A is gone, so a spoofed name can no longer become a minted token.WriteAgentToken, refreshed hourly and on every relaunch) already maintains.Mechanism
Hub side (
pkg/github/proxy_token_source.go,WriteAgentToken): under the flag, the freshly-minted tier-scoped token is diverted to an in-memory registry only the hub process can read, and the agent-visible cache file — the single choke point thatgh-wrapper.sh(GH_TOKEN),git-credential-hive.sh(password), and the manager'sGITHUB_TOKENenv push for the MCP server all read — receives the visibly-fake placeholderhive-proxy-injected-<agent>instead. No bash/manager changes needed: every agent-side consumer is converted by that one divert, tooling keeps functioning (syntactically-valid credential in place), and any leak of the placeholder is inert and self-diagnosing. The registry is package-level, not onAppAuth, becauseappAuthis replaced at runtime on key rotation and a proxy closure over one instance would strand it on the stale map.Proxy side (
rewriteGitHubAuth, called after the ACMM/repo/canary gates and before both forwarding branches):Authorizationheader is deleted unconditionally under the flag — even a credential smuggled to an agent through a side channel cannot be spent through the proxy. (Survey note:Authorizationis the only header GitHub honors for API/git auth today; the old?access_token=query param was removed by GitHub in 2020 and noX-*-tokenrequest-auth variant exists, so header stripping covers the surface.)token <t>for REST/GraphQL,Basic x-access-token:<t>for git smart HTTP paths (the exact shapegit-credential-hive.shproduced before, so upstream sees nothing new).Authorizationand 401s at GitHub. Deliberate: any fallback (shared cache, hive token) would let an unattributable process ride a real credential, recreating the pre-fix(proxy): stop trusting self-asserted agent identity as a fallback (N7, #3841) #3888 identity hole. The token source is not even consulted (tested).hive-internal, assigned only viaUIDMap.IsInternalUID, unspoofable) pass untouched — they legitimately carry the hive's own App credentials for mint/heartbeat/relay fulfillment./login/…) are strip-only — those authenticate the flow via the form body, and an App token there would be wrong.Survey finding: MITM widens under the flag (and why)
Today the proxy MITMs only
api.github.com;github.comand registered GHE hosts are opaque tunnels (NeedsMITM). That is fine without injection, but with it an opaque tunnel is a hole: the agent's git credential helper now serves the placeholder, so agit pushthrough an un-decrypted tunnel would reach GitHub carrying the placeholder and 401 — and a smuggled real credential would transit un-stripped. So under the flag (only),hostNeedsMITMwidens interception to every GitHub-family host. Injected git requests are additionally markedConnection: close, because the git branch switches to a raw relay after the first request's headers — a keep-alive follow-up on the same conn would stream past the rewrite; forcing per-request connections makes every git exchange re-enter the rewrite. Flag off: interception surface unchanged.Soak watch-items (why the default must stay off for now)
/logintraffic is intercepted; if that CLI does not trust the hive CA the flow breaks (the existingGIT_SSL_CAINFO-not-SSL_CERT_FILEcomment suggests sensitivity here). First flag-on spoke should verify Copilot login.git-receive-packhits the pre-existing "canary scan unavailable for git push" deny whenioscan.fail_closedis set — loud, but worth knowing before enabling both together.Explicitly out of scope (the follow-up that closes #1861)
HIVE_PROXY_INJECT_GH_AUTHdefault ON and removing agent-side token delivery entirely (cache files,GITHUB_TOKENenv push) once soaked — the issue closes there, not here.contribute_ws.go/contributor-relay.sh) and the opt-in pushbroker/mint WIF lane — separate credential paths, untouched.--scopedbypass fix (🐛 fix(credentials): stop--scopedserving the full App token from the shared cache #3947), per-agent minting on relaunch (🐛 fix(agents): mint the per-agent GitHub token on every relaunch path, not only Start #3967).Test evidence (fail-first / mutation-checked)
New tests:
pkg/proxy/auth_inject_test.go(7 tests, httptest-style fake upstream capturing raw bytes),pkg/github/proxy_token_source_test.go(2),pkg/config/proxy_inject_test.go(1). Each guard was mutation-checked — the guard removed, the suite re-run, the failure observed, the guard restored:Authorizationstrip deleted…AgentSuppliedAuthorizationNeverReachesUpstreamagent-supplied credential reached upstream…FlagOffByteIdenticalPassthrough…UnknownAgentNoInjectionNoFallbackWriteAgentTokendivert deleted…InjectionDivertsRealTokenToRegistryagent-readable cache contains the REAL tokenTestHostNeedsMITM_InjectionWidensInterceptiongithub.com/GHE not intercepted under flag…IdentifiedAgentGetsScopedTokenSuite runs (macOS,
-count=1):go test ./pkg/proxy/ -race— ok (51s, full package)go test ./pkg/github/— ok (46s, full package)go test ./pkg/config/— newTestProxyInjectGHAuthpasses;TestExportEmitsShellExportOnBothPaths+TestSharedCacheIsCreatedPrivatefail identically on cleanorigin/v4@9f9a65f2(verified in a pristine worktree) — pre-existing, unrelated (gh-app-token.sh script tests).Part of #1861