2459cd4fd8
Add ProofLiveReal (highest level, no driver yet) and enforce it at execute time: rotate --execute only runs drivers with proof >= LIVE-VM (MinExecuteProof). MOCK-ONLY/UNPROVEN drivers are skipped before any provider call unless --allow-mock-proven is passed. ExecuteResult gains Skipped/SkipReason; the CLI reports and audit-logs skips separately from failures. Closes the last structural gap where an emulator-only driver could touch a real credential unattended. Covered by execute_gate_test.go. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
4.8 KiB
Go
95 lines
4.8 KiB
Go
package rotate
|
|
|
|
// Proof tracking — DATA, deliberately kept OUT of the driver code.
|
|
//
|
|
// Every Rotator in this package 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, which the real deployment also configures.
|
|
//
|
|
// What differs between drivers is HOW their real-cutover path has been *validated*.
|
|
// That validation status is data, not behavior, so it lives here in one table
|
|
// rather than as prose scattered through (and easily confused with) the drivers.
|
|
// docs/ROTATION-PROOFS.md is the human-facing mirror of this same data; `incredigo
|
|
// rotate` surfaces it so a mock-only driver can never be mistaken for a live one.
|
|
|
|
// ProofLevel records how a driver's real-cutover behaviour has been validated.
|
|
type ProofLevel int
|
|
|
|
// Ordered lowest→highest: a higher level is a strictly stronger cutover proof.
|
|
// The execute-time gate (see execute.go) compares against ProofLiveVM.
|
|
const (
|
|
// ProofUnproven: no cutover proof recorded (e.g. the dry-run noop helper).
|
|
ProofUnproven ProofLevel = iota
|
|
// ProofMockOnly: 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.
|
|
ProofMockOnly
|
|
// ProofLiveVM: cutover proven against the REAL target software running in the
|
|
// sandbox VM (real PostgreSQL/MariaDB/Redis/wireguard-tools/Gitea/local files).
|
|
ProofLiveVM
|
|
// ProofLiveReal: cutover proven against a REAL, host-owned credential at the
|
|
// real provider (not the VM, not an emulator) — the highest level, recorded
|
|
// only after an authorized per-credential rotation on the safe-candidate ladder
|
|
// (ROADMAP M2). No driver carries this yet; promotions land here as data.
|
|
ProofLiveReal
|
|
)
|
|
|
|
// MinExecuteProof is the minimum proof level a driver must carry to run under
|
|
// `rotate --execute` WITHOUT the explicit --allow-mock-proven opt-in. Anything
|
|
// below this (MOCK-ONLY, UNPROVEN) is gated off by default so a driver never
|
|
// proven against real target software cannot touch a real credential unattended.
|
|
const MinExecuteProof = ProofLiveVM
|
|
|
|
// String renders the level as the token shown in the CLI and the manifest.
|
|
func (p ProofLevel) String() string {
|
|
switch p {
|
|
case ProofLiveReal:
|
|
return "LIVE-REAL"
|
|
case ProofLiveVM:
|
|
return "LIVE-VM"
|
|
case ProofMockOnly:
|
|
return "MOCK-ONLY"
|
|
default:
|
|
return "UNPROVEN"
|
|
}
|
|
}
|
|
|
|
// proofLevels maps a driver's Name() to how its real-cutover path was validated.
|
|
// This is the single source of truth; keep docs/ROTATION-PROOFS.md in sync.
|
|
//
|
|
// IMPORTANT honesty note: a driver running in the VM is NOT automatically 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.
|
|
var proofLevels = map[string]ProofLevel{
|
|
// proven against the real target software in the sandbox VM:
|
|
"postgres": ProofLiveVM, // real PostgreSQL (lab-provision-pg.sh)
|
|
"mysql": ProofLiveVM, // real MariaDB (lab-provision-dbclones.sh)
|
|
"redis": ProofLiveVM, // real redis-server(lab-provision-dbclones.sh)
|
|
"wireguard": ProofLiveVM, // real wg (lab-provision-wg.sh)
|
|
"gitea": ProofLiveVM, // real Gitea (lab-provision-gitea.sh)
|
|
"appsecret": ProofLiveVM, // real local files (lab-provision-appsec.sh)
|
|
"mongo": ProofLiveVM, // real mongod 8.0 (lab-provision-mongo.sh)
|
|
"k8s": ProofLiveVM, // real k3s v1.35 (lab-provision-k8s.sh)
|
|
|
|
// proven only against an emulator / mock:
|
|
"aws": ProofMockOnly, // moto mock AWS in VM; real AWS never hit
|
|
"sshkey": ProofMockOnly, // in-process SSH server; live VM POC not run
|
|
"openwrt": ProofMockOnly, // in-process SSH server; live QEMU deferred
|
|
"mullvad": ProofMockOnly, // httptest emulator; needs a paid account
|
|
"cloudflare": ProofMockOnly, // httptest emulator; no self-host
|
|
"ghactions": ProofMockOnly, // httptest emulator; no self-host
|
|
"gitlab": ProofMockOnly, // httptest emulator; GitLab CE too heavy for the VM
|
|
"npm": ProofMockOnly, // httptest emulator; real registry never hit
|
|
"gcp": ProofMockOnly, // httptest emulator (real RS256 JWT signing); no self-host
|
|
"twilio": ProofMockOnly, // httptest emulator; no self-host
|
|
"flyio": ProofMockOnly, // httptest GraphQL emulator; no self-host
|
|
"resend": ProofMockOnly, // httptest emulator; no self-host (real key needs a paid-ish account)
|
|
"vercel": ProofMockOnly, // httptest emulator; no self-host
|
|
"sendgrid": ProofMockOnly, // httptest emulator; no self-host
|
|
}
|
|
|
|
// ProofLevelOf returns the recorded proof level for a driver name. Unknown or
|
|
// proofless drivers (e.g. the noop dry-run helper) report ProofUnproven.
|
|
func ProofLevelOf(name string) ProofLevel { return proofLevels[name] }
|