//go:build linux package provisioner import ( "archive/tar" "bytes" "context" "filippo.io/age" "os" "path/filepath" "testing" ) func TestEncryptedBackupRestoreAndSurvivingFloor(t *testing.T) { state, floor, backups := persistentDirectory(t), persistentDirectory(t), persistentDirectory(t) password := []byte("synthetic backup password") initial, e := Initialize(state, floor, password) if e != nil { t.Fatal(e) } j, e := existingJournal(state, floor, password) if e != nil { t.Fatal(e) } payload := []byte("PRIVATE-BACKUP-OWNER@example.invalid") if _, e = j.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "empty", payload); e != nil { t.Fatal(e) } closeExisting(j) path := filepath.Join(backups, "current.age") info, e := Backup(state, floor, path, password) if e != nil || info.Sequence != 1 || len(info.Digest) != 64 { t.Fatal("backup", e) } data, e := os.ReadFile(path) if e != nil { t.Fatal(e) } if bytes.Contains(data, payload) || bytes.Contains(data, []byte(initial.Registry)) { t.Fatal("plaintext archive") } restored := persistentDirectory(t) got, e := Restore(restored, floor, path, password) if e != nil || got.Registry != initial.Registry || got.Sequence != 1 { t.Fatal("restore", e) } j, e = existingJournal(restored, floor, password) if e != nil { t.Fatal(e) } if _, e = j.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "empty", payload); e != nil { t.Fatal("restored private replay", e) } if _, e = j.append(context.Background(), testToken(5), testToken(7), "directory.activate-consumer", "pending", []byte("next private record")); e != nil { t.Fatal(e) } closeExisting(j) stale := persistentDirectory(t) if _, e = Restore(stale, floor, path, password); e != ErrRefused { t.Fatal("old archive accepted", e) } if _, e = Restore(persistentDirectory(t), floor, path, []byte("wrong")); e != ErrRefused { t.Fatal("wrong archive password", e) } if _, e = Backup(restored, floor, path, password); e != ErrRefused { t.Fatal("exclusive archive replaced", e) } } func TestRestoreRefusesUnauthenticatedCompletionAndLinks(t *testing.T) { state, floor, backups := persistentDirectory(t), persistentDirectory(t), persistentDirectory(t) password := []byte("synthetic malformed archive") if _, e := Initialize(state, floor, password); e != nil { t.Fatal(e) } path := filepath.Join(backups, "good.age") if _, e := Backup(state, floor, path, password); e != nil { t.Fatal(e) } data, e := os.ReadFile(path) if e != nil { t.Fatal(e) } damaged := filepath.Join(backups, "truncated.age") if e = os.WriteFile(damaged, data[:len(data)-10], 0600); e != nil { t.Fatal(e) } target := persistentDirectory(t) if _, e = Restore(target, floor, damaged, password); e != ErrRefused { t.Fatal("truncated encrypted stream accepted", e) } if _, e = os.Stat(filepath.Join(target, "credential.age")); !os.IsNotExist(e) { t.Fatal("unauthenticated archive published credentials") } recipient, e := age.NewScryptRecipient(string(password)) if e != nil { t.Fatal(e) } var ciphertext bytes.Buffer encrypted, e := age.Encrypt(&ciphertext, recipient) if e != nil { t.Fatal(e) } archive := tar.NewWriter(encrypted) if e = archive.WriteHeader(&tar.Header{Name: "registry.db", Mode: 0600, Typeflag: tar.TypeSymlink, Linkname: "outside"}); e != nil { t.Fatal(e) } if archive.Close() != nil || encrypted.Close() != nil { t.Fatal("synthetic archive") } link := filepath.Join(backups, "link.age") if e = os.WriteFile(link, ciphertext.Bytes(), 0600); e != nil { t.Fatal(e) } if _, e = Restore(persistentDirectory(t), floor, link, password); e != ErrRefused { t.Fatal("archive link accepted", e) } }