package cli import ( "archive/tar" "compress/gzip" "crypto/ed25519" "io" "os" "path/filepath" "strings" "testing" "time" agecrypt "filippo.io/age" "golang.org/x/crypto/ssh" "gitea.mynodes.xyz/ilker/llmgw/internal/config" "gitea.mynodes.xyz/ilker/llmgw/internal/store" ) func TestABackupIsEncryptedToItsRecipients(t *testing.T) { tmp := t.TempDir() etc := filepath.Join(tmp, "etc") _ = os.MkdirAll(filepath.Join(etc, "pki"), 0o700) _ = os.WriteFile(filepath.Join(etc, "pki", "ca-key.pem"), []byte("THE-CA-KEY"), 0o600) id, err := agecrypt.GenerateX25519Identity() if err != nil { t.Fatal(err) } to, err := config.ParseBackupRecipients([]string{id.Recipient().String()}) if err != nil { t.Fatal(err) } out := filepath.Join(tmp, "backups") path, err := writeBackup(t.Context(), "", etc, out, to) if err != nil { t.Fatal(err) } if !strings.HasSuffix(path, ".tar.gz.age") { t.Errorf("named %s", filepath.Base(path)) } raw, _ := os.ReadFile(path) if strings.Contains(string(raw), "THE-CA-KEY") { t.Fatal("the CA key is in the archive in the clear") } f, _ := os.Open(path) defer f.Close() plain, err := agecrypt.Decrypt(f, id) if err != nil { t.Fatalf("the recipient cannot open it: %v", err) } gz, err := gzip.NewReader(plain) if err != nil { t.Fatal(err) } tr := tar.NewReader(gz) found := false for { h, err := tr.Next() if err != nil { break } found = found || h.Name == "etc/llmgw/pki/ca-key.pem" } if !found { t.Error("the opened archive lacks the CA key") } // Seen and pruned like any other. if NewestBackup(out).IsZero() { t.Error("the control plane would not see an encrypted archive") } } func TestBackupRecipientsMustBeKeys(t *testing.T) { if _, err := config.ParseBackupRecipients([]string{"not a key"}); err == nil { t.Error("nonsense was taken as a recipient") } pub, _, _ := ed25519.GenerateKey(nil) sp, err := ssh.NewPublicKey(pub) if err != nil { t.Fatal(err) } if _, err := config.ParseBackupRecipients([]string{ strings.TrimSpace(string(ssh.MarshalAuthorizedKey(sp))) + " ops@example", }); err != nil { t.Errorf("an SSH key was refused: %v", err) } } func TestABackupHoldsTheStoreAndTheConfiguration(t *testing.T) { tmp := t.TempDir() dbPath := filepath.Join(tmp, "store.db") db, err := store.Open(t.Context(), dbPath) if err != nil { t.Fatal(err) } secret, err := db.ClusterSecret(t.Context(), "engine-key") if err != nil { t.Fatal(err) } _ = db.Close() etc := filepath.Join(tmp, "etc") _ = os.MkdirAll(filepath.Join(etc, "pki"), 0o700) _ = os.WriteFile(filepath.Join(etc, "server.yaml"), []byte("listen: :8080\n"), 0o644) _ = os.WriteFile(filepath.Join(etc, "pki", "ca-key.pem"), []byte("KEY"), 0o600) out := filepath.Join(tmp, "backups") path, err := writeBackup(t.Context(), dbPath, etc, out, nil) if err != nil { t.Fatal(err) } info, _ := os.Stat(path) if info.Mode().Perm() != 0o600 { t.Errorf("the archive holds the CA's key and is %v", info.Mode().Perm()) } files := map[string][]byte{} f, _ := os.Open(path) defer f.Close() gz, err := gzip.NewReader(f) if err != nil { t.Fatal(err) } tr := tar.NewReader(gz) for { h, err := tr.Next() if err == io.EOF { break } if err != nil { t.Fatal(err) } b, _ := io.ReadAll(tr) files[h.Name] = b } for _, want := range []string{"MANIFEST", "store.sqlite", "etc/llmgw/server.yaml", "etc/llmgw/pki/ca-key.pem"} { if _, ok := files[want]; !ok { t.Errorf("%s is not in the archive", want) } } // And the store in it is a working store, with what was in it. restored := filepath.Join(tmp, "restored.db") _ = os.WriteFile(restored, files["store.sqlite"], 0o600) rdb, err := store.Open(t.Context(), restored) if err != nil { t.Fatalf("the backed-up store does not open: %v", err) } defer rdb.Close() if got, _ := rdb.ClusterSecret(t.Context(), "engine-key"); got != secret { t.Error("the restored store lost the engine key secret; every engine would be locked out") } } func TestOldBackupsArePrunedAndTheNewestIsFound(t *testing.T) { dir := t.TempDir() for i, stamp := range []string{"20260101T000000Z", "20260102T000000Z", "20260103T000000Z"} { p := filepath.Join(dir, backupPrefix+"h-"+stamp+".tar.gz") _ = os.WriteFile(p, []byte("x"), 0o600) mt := time.Date(2026, 1, 1+i, 0, 0, 0, 0, time.UTC) _ = os.Chtimes(p, mt, mt) } _ = os.WriteFile(filepath.Join(dir, "someone-elses-file"), []byte("x"), 0o600) _ = os.WriteFile(filepath.Join(dir, backupPrefix+"h-20260104T000000Z.tar.gz.part"), []byte("x"), 0o600) removed, err := pruneBackups(dir, 2) if err != nil || removed != 1 { t.Fatalf("removed %d, %v", removed, err) } if _, err := os.Stat(filepath.Join(dir, "someone-elses-file")); err != nil { t.Error("a file the backup did not write was removed") } newest := NewestBackup(dir) if !newest.Equal(time.Date(2026, 1, 3, 0, 0, 0, 0, time.UTC)) { t.Errorf("newest %v; an interrupted .part must not count", newest) } if !NewestBackup(filepath.Join(dir, "none")).IsZero() { t.Error("a missing directory reported a backup") } } func TestABackupDirectoryTheControlPlaneCannotListIsSaid(t *testing.T) { dir := t.TempDir() _ = os.Chmod(dir, 0o700) if unlistable(dir) == "" { t.Error("a directory only its owner can list was taken as visible") } _ = os.Chmod(dir, 0o755) if why := unlistable(dir); why != "" { t.Errorf("a listable directory: %s", why) } } func TestNoScratchCopyOfTheStoreIsLeftBehind(t *testing.T) { // The snapshot is made before it is archived. Made straight into the // listable archive directory, it was readable by anyone for that time. tmp := t.TempDir() dbPath := filepath.Join(tmp, "store.db") db, err := store.Open(t.Context(), dbPath) if err != nil { t.Fatal(err) } _ = db.Close() out := filepath.Join(tmp, "backups") if _, err := writeBackup(t.Context(), dbPath, t.TempDir(), out, nil); err != nil { t.Fatal(err) } entries, _ := os.ReadDir(out) for _, e := range entries { if strings.HasPrefix(e.Name(), ".store") { t.Errorf("a scratch copy of the store was left behind: %s", e.Name()) } } } func TestAStoreOfSeveralHostsIsGivenToPgDumpAsLibpqLists(t *testing.T) { // The address of a Patroni cluster: url.Parse read its host as one name, // and pg_dump reached the database only by falling through to the second. env, err := pgEnv("postgres://llmgw:s3cret@10.0.0.149:5432,10.0.0.150/llmgw?sslmode=disable&target_session_attrs=read-write") if err != nil { t.Fatal(err) } got := strings.Join(env, " ") for _, want := range []string{"PGHOST=10.0.0.149,10.0.0.150", "PGPORT=5432,5432", "PGUSER=llmgw", "PGPASSWORD=s3cret", "PGDATABASE=llmgw", "PGSSLMODE=disable", "PGTARGETSESSIONATTRS=prefer-standby"} { if !strings.Contains(got, want) { t.Errorf("%s is not in %s", want, got) } } one, err := pgEnv("postgres://u:p@db.internal:6432/llmgw") if err != nil || !strings.Contains(strings.Join(one, " "), "PGHOST=db.internal PGPORT=6432") { t.Errorf("one host: %v, %v", one, err) } if _, err := pgEnv("postgres://u:p@a:5432,:5432/x"); err == nil { t.Error("an empty host in the list was accepted") } }