59af53dcc0
Implements the Rotator interface across all four rotation patterns, each a self-contained one-file driver self-registering via init(): in-place DB password postgres, mysql (3-stmt unprivileged fallback), redis local keypair + propagate sshkey (ed25519), wireguard (clamped curve25519) provider-API token gitea PAT, mullvad device key cloud-key self-identifying aws IAM access key (hand-rolled SigV4, no SDK dep) Spine (execute.go) enforces Hard Rule #2: backup -> rotate -> verify(new) -> store -> re-read+verify -> revoke-old; dryrun.go keeps --execute gated. Each driver ships a table-driven test proving real cutover (old secret stops authenticating) and asserting no secret substring leaks to argv/Meta/errors. Promotes golang.org/x/crypto to a direct dependency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package rotate
|
|
|
|
import (
|
|
"context"
|
|
|
|
"incredigo/internal/discover"
|
|
"incredigo/internal/vault"
|
|
)
|
|
|
|
// DryRunResult records, without any secret, what the rotation spine did for one
|
|
// credential during a dry run.
|
|
type DryRunResult struct {
|
|
Credential discover.Credential
|
|
Driver string
|
|
Rotated bool // a new secret was minted in-vault
|
|
Verified bool // the new secret passed Verify
|
|
Revoked bool // RevokeOld was invoked (no real effect for the noop driver)
|
|
Err error // first error encountered for this credential, if any
|
|
}
|
|
|
|
// DryRun walks the post-backup rotation spine — Detect → Rotate → Verify →
|
|
// RevokeOld — for every credential that driver d claims, WITHOUT storing
|
|
// anything in gopass and WITHOUT persisting the minted secret (it is Forgotten
|
|
// from the vault immediately after). It exists so the full flow can be exercised
|
|
// against the noop driver (docs/ROTATION.md §6 step 1) touching no service.
|
|
//
|
|
// The MANDATORY backup gate (Snapshot) is the caller's responsibility and must
|
|
// already have succeeded; DryRun does not perform it.
|
|
func DryRun(ctx context.Context, d Rotator, creds []discover.Credential, v *vault.Vault) []DryRunResult {
|
|
var out []DryRunResult
|
|
for _, c := range creds {
|
|
if !d.Detect(c) {
|
|
continue
|
|
}
|
|
res := DryRunResult{Credential: c, Driver: d.Name()}
|
|
|
|
newH, err := d.Rotate(ctx, c, v)
|
|
if err != nil {
|
|
res.Err = err
|
|
out = append(out, res)
|
|
continue
|
|
}
|
|
res.Rotated = true
|
|
|
|
// Verify-before-revoke: only proceed to revoke once the new secret checks out.
|
|
if err := d.Verify(ctx, newH, v); err != nil {
|
|
res.Err = err
|
|
v.Forget(newH)
|
|
out = append(out, res)
|
|
continue
|
|
}
|
|
res.Verified = true
|
|
|
|
// A real run stores the new secret in gopass here; a dry run does not.
|
|
if err := d.RevokeOld(ctx, c, v); err != nil {
|
|
res.Err = err
|
|
} else {
|
|
res.Revoked = true
|
|
}
|
|
|
|
v.Forget(newH) // never persist the minted secret in a dry run
|
|
out = append(out, res)
|
|
}
|
|
return out
|
|
}
|