Add gitlab, cloudflare, ghactions (MOCK-ONLY) and appsecret (LIVE-VM) one-file Rotators, each with a table-driven cutover-proof + leak-check test. gitlab/ cloudflare/ghactions are in-place SaaS-token rolls (self/rotate, value-roll, sealed-secret overwrite) so RevokeOld is a no-op; ghactions seals via nacl/box.SealAnonymous (no new go.mod dep). appsecret regenerates a local app signing secret and rewrites it in place across every target file (atomic, mode- preserving, redacted errors), discoverable via exact-match app-signing key names in env.go. Keep real-rotation code distinct from mock code: how each driver's cutover was validated lives as DATA in internal/rotate/proofs.go (single source of truth) + docs/ROTATION-PROOFS.md, surfaced as a PROOF column in `incredigo rotate` so a MOCK-ONLY driver can never be mistaken for a LIVE-VM one. appsecret proven LIVE-VM against real local files in the sandbox VM. 82 tests green, -race clean on rotate/sink/vault. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.9 KiB
Rotation cutover proofs
This file is the human-facing mirror of internal/rotate/proofs.go. That Go table is
the single source of truth (incredigo rotate surfaces it as the PROOF column); this
document explains what each level means and records the basis for every driver's level.
Keep the two in sync — if you change one, change the other.
Why this exists
Every Rotator in internal/rotate contains only real rotation code: it talks to a
real service (HTTP API, DB client, SSH, local files). None of them contain simulation
branches or test awareness — the only thing a test injects is an endpoint / HTTPClient /
binary path, which a real deployment also configures.
What differs between drivers is how their real-cutover path has been validated. That is
data, not behaviour, so it lives in one table (here + proofs.go) instead of as prose
scattered through — and easily confused with — the driver code. This is the guardrail that
stops a mock-only driver from ever being mistaken for a live one.
Levels
| Level | Meaning |
|---|---|
| LIVE-VM | Cutover proven against the real target software running in the sandbox VM (real PostgreSQL / MariaDB / Redis / wg / Gitea / local files). |
| MOCK-ONLY | Cutover proven only against an emulator — our own httptest / in-process SSH server, or a third-party mock (e.g. moto for AWS). NOT proven against the real target service. |
| UNPROVEN | No cutover proof recorded (e.g. the dry-run noop helper). |
Honesty note: running in the VM does not automatically make a driver LIVE-VM. AWS ran in the VM but only against moto (a mock AWS), so it is MOCK-ONLY. LIVE-VM means the real target software validated the cutover.
Current status
LIVE-VM — proven against real target software in the sandbox VM
| Driver | Real target | Provisioner |
|---|---|---|
postgres |
real PostgreSQL | lab-provision-pg.sh |
mysql |
real MariaDB | lab-provision-dbclones.sh |
redis |
real redis-server |
lab-provision-dbclones.sh |
wireguard |
real wg |
lab-provision-wg.sh |
gitea |
real Gitea | lab-provision-gitea.sh |
appsecret |
real local files | lab-provision-appsec.sh |
MOCK-ONLY — proven only against an emulator / mock
| Driver | Emulator | Why not (yet) LIVE-VM |
|---|---|---|
aws |
moto mock AWS in VM |
real AWS never hit |
sshkey |
in-process SSH server | live VM POC not run |
openwrt |
in-process SSH server | live QEMU deferred |
mullvad |
httptest emulator |
needs a paid account |
cloudflare |
httptest emulator |
no self-host |
ghactions |
httptest emulator (holds the libsodium box keypair, decrypts the sealed PUT) |
no self-host |
gitlab |
httptest emulator |
GitLab CE too heavy for the VM |
What the MOCK-ONLY emulators actually prove
The emulators are not stubs that rubber-stamp success — each enforces the real protocol so the test exercises the driver's true cutover logic:
- gitlab —
self/rotatemints a new PAT and revokes the caller's old one; the test assertsVerify(old)fails afterRotate, proving the revoke really happened. - cloudflare —
PUT .../valuerolls the value for the same token id and invalidates the previous value; the test asserts the id is unchanged andVerify(old)fails. - ghactions — the emulator owns a real NaCl
boxkeypair, serves the public key, and onPUTdecrypts the sealed value and stores the plaintext; the test asserts the decrypted value equals the new value the driver put in the rebuilt blob — proving the sealed-box encryption is correct, not simulated.
Promoting any of these to LIVE-VM only requires standing up the real service (a self-hosted GitLab CE / a real Cloudflare token / a throwaway GitHub repo) and re-running the same driver against it — no driver code changes.