Files
incredigo/docs/ROTATION-PROOFS.md
T
leetcrypt 35f19c007a rotate: 6 service-API drivers (mongo, npm, gcp, twilio, flyio, k8s)
Each driver is real-service code only (no test awareness; tests inject just an
HTTPClient/Bin), following the established patterns:

- mongo: in-place changeUserPassword over the old connection; no secret on argv
  (host/db on the URI, both passwords on stdin, output scrubbed).
- npm:   create token -> /-/whoami -> delete-by-key; blob records the key.
- gcp:   service-account KEY rotation; mints an RS256 JWT signed with the SA's
  own private key (crypto/rsa, stdlib) to self-auth the IAM create/verify/delete.
- twilio: API Key create -> get -> delete, Basic auth (KeySid/secret).
- flyio: GraphQL — a managing token mints/deletes the rotated deploy token.
- k8s:   create a service-account-token Secret, await the populated token,
  verify against the SA, delete the old Secret (invalidates the old token).

All 6 register their honest proof level (MOCK-ONLY) in proofs.go +
ROTATION-PROOFS.md; mongo/npm/k8s are LIVE-VM candidates pending real-target VM
POCs. Emulators enforce credential validity (gcp verifies the JWT signature),
tests assert Verify(old) fails after revoke + a secret-leak canary.

Full suite: 103 green, -race clean on rotate/sink/vault.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 15:57:59 -07:00

5.7 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
mongo fake-mongosh shell stub (auths against a state file) real-MongoDB VM POC not yet run
npm httptest registry emulator real registry never hit
gcp httptest IAM emulator (verifies the driver's RS256 JWT against the key's public key) no self-host
twilio httptest emulator (Basic-auth Keys resource) no self-host
flyio httptest GraphQL emulator no self-host
k8s httptest apiserver emulator (token-Secret create/delete) real k3s POC not yet run

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:

  • gitlabself/rotate mints a new PAT and revokes the caller's old one; the test asserts Verify(old) fails after Rotate, proving the revoke really happened.
  • cloudflarePUT .../value rolls the value for the same token id and invalidates the previous value; the test asserts the id is unchanged and Verify(old) fails.
  • ghactions — the emulator owns a real NaCl box keypair, serves the public key, and on PUT decrypts 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.
  • npm / twilio / k8s — each emulator enforces credential validity (a token/key authenticates only while it exists) and the test asserts Verify(old) fails after RevokeOld — proving the create→verify→revoke cutover really happened, old credential dead, new one alive.
  • gcp — the emulator verifies the RS256 JWT signature the driver mints against the public key recorded for the key's kid; it only issues an access token for a JWT that actually verifies, and DELETE removes the key — so the test proves real service-account self-auth (genuine crypto/rsa signing) and the cutover (revoked key can no longer mint a token).
  • flyio — a managing token mints/deletes the deploy token over GraphQL; the deploy token authenticates the app query only while it exists, so the test proves the managing-token → deploy-token rotation and that the revoked deploy token stops working.
  • mongo — the fake mongosh authenticates db.auth against a password held in a state file and only changeUserPassword updates it; the test proves the in-place cutover (old password rejected after rotation). No password ever reaches mongosh's argv.

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 / a real MongoDB / a real npm registry / a k3s cluster) and re-running the same driver against it — no driver code changes.