99 lines
3.9 KiB
Go
99 lines
3.9 KiB
Go
package store
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// R-1182: abandoning ONE set-aside off-site copy removes only the package that opens THAT copy. Before 2026-10-21 the
|
|
// purge was customer-wide (audits/dataloss-hunt-2026-10-20, C1 reviewer 3, K2): the package of a second set-aside copy
|
|
// still on the Storage Box went with it.
|
|
func TestR1182_AbandoningOneCopyKeepsTheOtherCopysPackage(t *testing.T) {
|
|
s := newTestStore(t)
|
|
const cust = "C"
|
|
const repo = "/home/felhom/C/repo"
|
|
if err := s.UpsertHost(&Host{HostID: "C-h", CustomerID: cust, APIKey: "k"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A row retained before the tag existed (legacy): unknown copy — must be kept.
|
|
if _, err := s.db.Exec(`INSERT INTO host_escrow_superseded (host_id, blob, created_at, restic_pw_sha256, identity_blob) VALUES ('C-h', x'01', '2026-01-01', 'SHA_LEGACY', x'02')`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
save := func(sha string) {
|
|
t.Helper()
|
|
if _, _, err := s.SaveHostEscrow("C-h", []byte("k-"+sha), "fp", "zk", "2026-10-01T00:00:00Z", sha); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.SaveHostDRBundle("C-h", []byte("id-"+sha)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
save("SHA_1") // key 1 writes the repository
|
|
save("SHA_2") // key 1 retained ('live'); the box set the old copy aside:
|
|
if n, err := s.TagRetainedEscrowSetAside(cust, repo+".orphaned-20261001"); err != nil || n != 1 {
|
|
t.Fatalf("first set-aside tagged %d err=%v, want 1 (key 1)", n, err)
|
|
}
|
|
save("SHA_3") // key 2 retained; a second copy set aside:
|
|
if n, err := s.TagRetainedEscrowSetAside(cust, repo+".orphaned-20261015"); err != nil || n != 1 {
|
|
t.Fatalf("second set-aside tagged %d err=%v, want 1 (key 2)", n, err)
|
|
}
|
|
|
|
// The household abandons the FIRST copy; the hub deletes it.
|
|
id, err := s.CreateOffsiteAbandon(cust, repo+".orphaned-20261001", time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MarkOffsiteAbandonDeleted(id); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if left, _ := s.AbandonedEscrowLeft(cust); left != 1 {
|
|
t.Fatalf("before the purge, packages of deleted copies = %d, want 1", left)
|
|
}
|
|
n, err := s.PurgeAbandonedEscrowForCustomer(cust)
|
|
if err != nil || n != 1 {
|
|
t.Fatalf("purge removed %d err=%v, want exactly 1 (key 1)", n, err)
|
|
}
|
|
kept := map[string]bool{}
|
|
rows, _ := s.ListSupersededEscrow("C-h")
|
|
for _, r := range rows {
|
|
kept[r.ResticPwSHA256] = true
|
|
}
|
|
if !kept["SHA_2"] {
|
|
t.Fatal("the package that opens the SECOND set-aside copy was purged — R-1182")
|
|
}
|
|
if !kept["SHA_LEGACY"] {
|
|
t.Fatal("a legacy package (unknown copy) was purged by a guess")
|
|
}
|
|
if kept["SHA_1"] {
|
|
t.Fatal("the abandoned copy's package survived the purge")
|
|
}
|
|
if cur, _ := s.GetHostEscrow("C-h"); cur == nil || cur.ResticPwSHA256 != "SHA_3" {
|
|
t.Fatalf("the CURRENT package was touched: %+v", cur)
|
|
}
|
|
if left, _ := s.AbandonedEscrowLeft(cust); left != 0 {
|
|
t.Fatalf("after the purge, packages of deleted copies = %d, want 0", left)
|
|
}
|
|
if n2, _ := s.PurgeAbandonedEscrowForCustomer(cust); n2 != 0 {
|
|
t.Fatalf("second purge removed %d, want 0 (idempotent)", n2)
|
|
}
|
|
}
|
|
|
|
// A package whose key is the box's CURRENT key is not tagged by a move-aside: it opens the new repository (or holds
|
|
// an older whole-guest key, R-366), not the copy set aside.
|
|
func TestR1182_TheCurrentKeysPackageIsNotTaggedToASetAsideCopy(t *testing.T) {
|
|
s := newTestStore(t)
|
|
if err := s.UpsertHost(&Host{HostID: "C-h", CustomerID: "C", APIKey: "k"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := s.SaveHostEscrow("C-h", []byte("k1"), "FP1", "zk", "2026-10-01T00:00:00Z", "SHA_X"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// same repository password, new whole-guest key → retained (R-366) with the SAME restic hash
|
|
if _, _, err := s.SaveHostEscrow("C-h", []byte("k2"), "FP2", "zk", "2026-10-02T00:00:00Z", "SHA_X"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n, err := s.TagRetainedEscrowSetAside("C", "/r.orphaned-20261003"); err != nil || n != 0 {
|
|
t.Fatalf("tagged %d err=%v, want 0", n, err)
|
|
}
|
|
}
|