15f7529a1c
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
4.6 KiB
Go
96 lines
4.6 KiB
Go
package reconcile
|
|
|
|
import "testing"
|
|
|
|
// The storage-authorization redesign: a USER-DATA data-bearing wipe is customer-confirmable
|
|
// (durable-id-bound, no operator signature); SYSTEM and BACKUP stay operator-signature ONLY, and a
|
|
// `confirmed:true` claim on them is REFUSED by role (a compromised controller can't relabel a
|
|
// protected device to walk the gate). These assert the gate's tiering — the non-hollow checks the
|
|
// spec calls for: "the gate refuses a `confirmed` wipe on system/backup (assert no exec); a user-data
|
|
// confirmed wipe binds to the durable id (a mismatched id is refused)".
|
|
|
|
const durA = "byid:wwn-0x5000c500a"
|
|
const durB = "byuuid:1111-2222"
|
|
|
|
// user-data + confirmed + matching durable id → allowed (customer-confirmable), audited with the id.
|
|
func TestStorageWipe_UserDataConfirmed_Allowed(t *testing.T) {
|
|
aud := &captureAudit{}
|
|
g := NewGate(nil, testHost, aud, nil) // NO verifier pinned — proves no operator signature is needed
|
|
d := g.AuthorizeStorageWipe(StorageWipeAuthz{
|
|
HostID: testHost, Role: StorageRoleUserData,
|
|
DeviceDurableID: durA, Confirmed: true, ConfirmDurableID: durA,
|
|
}, nil)
|
|
if !d.Allowed || d.Disposition != CustomerConfirmable || d.Reason != ReasonCustomerConfirmed {
|
|
t.Fatalf("user-data confirmed+matching: got allowed=%v disp=%s reason=%s", d.Allowed, d.Disposition, d.Reason)
|
|
}
|
|
if len(aud.recs) != 1 || !aud.recs[0].Allowed || aud.recs[0].DurableID != durA {
|
|
t.Fatalf("customer-confirmed wipe must be audited with the durable id: %+v", aud.recs)
|
|
}
|
|
}
|
|
|
|
// user-data + confirmed but the confirmation binds to a DIFFERENT disk's durable id → refused
|
|
// (binding_mismatch). A confirmation for one disk can't wipe another.
|
|
func TestStorageWipe_UserDataDurableMismatch_Refused(t *testing.T) {
|
|
g := NewGate(nil, testHost, &captureAudit{}, nil)
|
|
d := g.AuthorizeStorageWipe(StorageWipeAuthz{
|
|
HostID: testHost, Role: StorageRoleUserData,
|
|
DeviceDurableID: durA, Confirmed: true, ConfirmDurableID: durB, // confirms B, device is A
|
|
}, nil)
|
|
if d.Allowed || d.Reason != ReasonBindingMismatch {
|
|
t.Fatalf("durable-id mismatch: got allowed=%v reason=%s, want refused binding_mismatch", d.Allowed, d.Reason)
|
|
}
|
|
}
|
|
|
|
// user-data + an UNRESOLVABLE device durable id fails safe (refused) even when confirmed.
|
|
func TestStorageWipe_UserDataNoDurable_Refused(t *testing.T) {
|
|
g := NewGate(nil, testHost, &captureAudit{}, nil)
|
|
d := g.AuthorizeStorageWipe(StorageWipeAuthz{
|
|
HostID: testHost, Role: StorageRoleUserData,
|
|
DeviceDurableID: "", Confirmed: true, ConfirmDurableID: "",
|
|
}, nil)
|
|
if d.Allowed {
|
|
t.Fatal("a wipe with no resolvable durable id must not be allowed even when confirmed")
|
|
}
|
|
}
|
|
|
|
// user-data, NOT confirmed → pending_confirmation (ask the customer; not a signature).
|
|
func TestStorageWipe_UserDataUnconfirmed_PendingConfirmation(t *testing.T) {
|
|
g := NewGate(nil, testHost, &captureAudit{}, nil)
|
|
d := g.AuthorizeStorageWipe(StorageWipeAuthz{
|
|
HostID: testHost, Role: StorageRoleUserData, DeviceDurableID: durA,
|
|
}, nil)
|
|
if d.Allowed || d.Disposition != CustomerConfirmable || d.Reason != ReasonPendingConfirmation {
|
|
t.Fatalf("user-data unconfirmed: got allowed=%v disp=%s reason=%s", d.Allowed, d.Disposition, d.Reason)
|
|
}
|
|
}
|
|
|
|
// THE HEADLINE: a `confirmed:true` wipe on a SYSTEM device is REFUSED — it falls to the
|
|
// operator-signature path (no signer pinned → pending_signature), NOT customer-confirmable. The
|
|
// customer's confirmation is ignored BY ROLE. Same for BACKUP.
|
|
func TestStorageWipe_SystemConfirmedTrue_RefusedBySignature(t *testing.T) {
|
|
for _, role := range []string{StorageRoleSystem, StorageRoleBackup} {
|
|
g := NewGate(nil, testHost, &captureAudit{}, nil)
|
|
d := g.AuthorizeStorageWipe(StorageWipeAuthz{
|
|
HostID: testHost, Role: role,
|
|
DeviceDurableID: durA, Confirmed: true, ConfirmDurableID: durA, // a matching confirmation — must NOT help
|
|
}, nil)
|
|
if d.Allowed {
|
|
t.Fatalf("role=%s: a confirmed wipe of a protected device was ALLOWED — invariant violated", role)
|
|
}
|
|
if d.Disposition != Destructive || d.Reason != ReasonPendingSignature {
|
|
t.Fatalf("role=%s: got disp=%s reason=%s, want destructive/pending_signature", role, d.Disposition, d.Reason)
|
|
}
|
|
}
|
|
}
|
|
|
|
// An UNKNOWN/empty role fails safe to the protected (operator-signature) path, never user-data.
|
|
func TestStorageWipe_UnknownRole_FailsSafeDestructive(t *testing.T) {
|
|
g := NewGate(nil, testHost, &captureAudit{}, nil)
|
|
d := g.AuthorizeStorageWipe(StorageWipeAuthz{
|
|
HostID: testHost, Role: "", Confirmed: true, DeviceDurableID: durA, ConfirmDurableID: durA,
|
|
}, nil)
|
|
if d.Allowed || d.Disposition != Destructive {
|
|
t.Fatalf("unknown role must fail safe to destructive: got allowed=%v disp=%s", d.Allowed, d.Disposition)
|
|
}
|
|
}
|