rotate/gitea: multi-reference blast-and-cutover (LIVE-VM)

Rotate one Gitea PAT and rewrite it in EVERY on-disk reference in lockstep,
so a real in-use token can rotate without breaking git auth. The blob carries
one ref=<file> per reference; Rotate mints the new token, proves it
authenticates, then rewrites the old->new token literal in each ref file
(atomic temp+rename, minGiteaTokenLen-guarded), Verify asserts the new token
is present in each ref, and the spine revokes the old token last. A dead token
is never written into a git config; any failure leaves every ref on a live token.

Adds internal/rotate/gitea_refs.go (read-only reference enumerator) + driver and
enumerator unit tests. Proven end-to-end against real Gitea 1.25.0 in the sandbox
VM via lab/lab-gitea-blast-vm.sh (GITEA_BLAST_VM_OK): one rotation rewrote a fake
.git-credentials, an embedded git-remote URL, and a tea config; new->200, old->401.

Two documented follow-ons before any in-use host PAT: gopass-entry-sync (PAT
duplicated across other gopass entries) and token-scope-cloning (preserve the old
token's repo scopes on mint, like sendgrid).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-19 13:25:52 -07:00
parent 9f227bb05f
commit 8dbb2f21fd
7 changed files with 622 additions and 18 deletions
+25 -1
View File
@@ -53,7 +53,31 @@ credential unattended.
> throwaway token was chosen so the driver's real lifecycle earns LIVE-REAL at **zero blast
> radius** — a genuine in-use PAT is referenced in `~/.git-credentials`, embedded git-remote
> URLs and `~/.config/tea/config.yml` as well as gopass, so rotating one safely first requires
> the multi-reference **blast-and-cutover** (next work item), not just the single-blob driver.
> the multi-reference **blast-and-cutover**, not just the single-blob driver.
#### Multi-reference blast-and-cutover — proven LIVE-VM
The cutover a real *in-use* Gitea PAT needs is now built and proven against real local
Gitea (1.25.0) in the VM (`lab-gitea-blast-vm.sh``GITEA_BLAST_VM_OK`). The credential
blob carries one `ref=<file>` per on-disk reference; `Rotate` mints the new token, proves
it authenticates (`GET /user`), **then** rewrites the old→new token literal in **every**
reference file atomically, and `Verify` asserts the new token is present in each before the
spine revokes the old one **last**. Proof run: one rotation rewrote a fake `~/.git-credentials`
line, an embedded git-remote URL, and a `tea` config in lockstep; new token → `200`, old
token → `401`; the rewritten `.git-credentials` still authenticates at the git transport.
The old→new replacement is keyed on the 40-hex token literal, length-guarded
(`minGiteaTokenLen`), and atomic per file (temp+rename) — the same pattern the `appsecret`
driver uses. Enumerating the reference set is `internal/rotate/gitea_refs.go` (read-only).
> **Two documented follow-ons** (surfaced by the proof, deliberately out of this iteration):
> - **gopass-entry-sync** — the PAT also lives in *other* gopass entries; the driver rewrites
> only files named in `ref=`. A spine hook passing old+new token to a gopass-sweep step is
> needed before an in-use PAT that is duplicated across gopass entries can rotate cleanly.
> - **token-scope-cloning** — the new token is minted with fixed `write:user`/`read:user`
> scopes (enough to manage tokens); a token used for **git operations** needs its repo
> scopes preserved, i.e. clone the old token's scopes on create (as the `sendgrid` driver
> clones API-key scopes). Until then a rotated git-auth PAT authenticates but may `403` on
> repo ops — a token-scope matter, not a cutover failure.
### LIVE-VM — proven against real target software in the sandbox VM