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>
75 lines
2.3 KiB
Go
75 lines
2.3 KiB
Go
package rotate
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"incredigo/internal/discover"
|
|
"incredigo/internal/vault"
|
|
)
|
|
|
|
// TestDryRunSpine confirms DryRun walks rotate→verify→revoke for every detected
|
|
// credential and persists nothing: the minted secret is Forgotten, and the
|
|
// original credential is left intact.
|
|
func TestDryRunSpine(t *testing.T) {
|
|
v := vault.New()
|
|
defer v.Purge()
|
|
|
|
old := []byte("OLD-VALUE")
|
|
oldH := v.Store(append([]byte(nil), old...))
|
|
creds := []discover.Credential{
|
|
{Source: "aws", Identity: "default / AKIA…REDACTED", Secret: oldH},
|
|
{Source: "env", Identity: ".env / API_TOKEN"},
|
|
}
|
|
|
|
d := &NoopRotator{}
|
|
results := DryRun(context.Background(), d, creds, v)
|
|
|
|
if len(results) != 2 {
|
|
t.Fatalf("got %d results, want 2", len(results))
|
|
}
|
|
for _, r := range results {
|
|
if r.Err != nil {
|
|
t.Fatalf("%s: unexpected error: %v", r.Credential.Identity, r.Err)
|
|
}
|
|
if !r.Rotated || !r.Verified || !r.Revoked {
|
|
t.Fatalf("%s: spine incomplete: rotated=%t verified=%t revoked=%t",
|
|
r.Credential.Identity, r.Rotated, r.Verified, r.Revoked)
|
|
}
|
|
}
|
|
|
|
// Every credential was revoked exactly once, after verify.
|
|
if got := len(d.Revoked()); got != 2 {
|
|
t.Fatalf("revoked %d, want 2", got)
|
|
}
|
|
|
|
// The original secret must remain readable — a dry run persists nothing and
|
|
// disturbs nothing.
|
|
ob, err := v.Open(oldH)
|
|
if err != nil {
|
|
t.Fatalf("old secret disturbed: %v", err)
|
|
}
|
|
if string(ob.Bytes()) != string(old) {
|
|
t.Fatal("old secret value changed during dry run")
|
|
}
|
|
}
|
|
|
|
// TestDryRunDetectFilter confirms DryRun skips credentials a driver does not
|
|
// claim, so a real (selective) driver only touches its own kind.
|
|
func TestDryRunDetectFilter(t *testing.T) {
|
|
v := vault.New()
|
|
defer v.Purge()
|
|
|
|
creds := []discover.Credential{{Source: "aws", Identity: "a"}, {Source: "env", Identity: "b"}}
|
|
results := DryRun(context.Background(), onlyAWS{&NoopRotator{}}, creds, v)
|
|
if len(results) != 1 || results[0].Credential.Source != "aws" {
|
|
t.Fatalf("expected only the aws credential, got %+v", results)
|
|
}
|
|
}
|
|
|
|
// onlyAWS is a minimal test driver that claims only aws credentials, reusing the
|
|
// noop's rotate/verify/revoke. It embeds a pointer so it carries no copied lock.
|
|
type onlyAWS struct{ *NoopRotator }
|
|
|
|
func (onlyAWS) Detect(c discover.Credential) bool { return c.Source == "aws" }
|