| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package backup
- import (
- "archive/zip"
- "bytes"
- "os"
- "path/filepath"
- "strings"
- "testing"
- )
- func TestCreateAndInspectBackup(t *testing.T) {
- tempDir := t.TempDir()
- debtikDir := filepath.Join(tempDir, "debtik")
- netDir := filepath.Join(tempDir, "network")
- storeDir := filepath.Join(tempDir, "backups")
- // Create test structure
- if err := os.MkdirAll(filepath.Join(debtikDir, "certs"), 0755); err != nil {
- t.Fatal(err)
- }
- if err := os.MkdirAll(netDir, 0755); err != nil {
- t.Fatal(err)
- }
- metadataContent := []byte(`{"version": 1, "description": "test"}`)
- keyContent := []byte(`super-secret-ssh-key`)
- certContent := []byte(`fake-cert-data`)
- ifacesContent := []byte(`auto eth0` + "\n" + `iface eth0 inet dhcp`)
- if err := os.WriteFile(filepath.Join(debtikDir, "metadata.json"), metadataContent, 0644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(debtikDir, "ssh_host_ed25519_key"), keyContent, 0600); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(debtikDir, "certs", "server.crt"), certContent, 0644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(netDir, "interfaces.cfg"), ifacesContent, 0644); err != nil {
- t.Fatal(err)
- }
- mgr := NewManager(debtikDir, netDir, storeDir, nil)
- // Create backup
- zipBytes, manifest, err := mgr.CreateBackup("Unit test backup")
- if err != nil {
- t.Fatalf("CreateBackup failed: %v", err)
- }
- if len(zipBytes) == 0 {
- t.Fatal("Expected non-empty zip bytes")
- }
- if manifest.Comment != "Unit test backup" {
- t.Errorf("Unexpected manifest comment: %s", manifest.Comment)
- }
- if len(manifest.Files) != 4 {
- t.Fatalf("Expected 4 files in manifest, got %d", len(manifest.Files))
- }
- // Inspect backup
- r := bytes.NewReader(zipBytes)
- inspected, err := mgr.InspectBackup(r, int64(len(zipBytes)))
- if err != nil {
- t.Fatalf("InspectBackup failed: %v", err)
- }
- if inspected.Hostname != manifest.Hostname {
- t.Errorf("Hostname mismatch: %s != %s", inspected.Hostname, manifest.Hostname)
- }
- if len(inspected.Files) != 4 {
- t.Errorf("Expected 4 files in inspected manifest, got %d", len(inspected.Files))
- }
- }
- func TestRestoreBackup(t *testing.T) {
- tempDir := t.TempDir()
- debtikDir := filepath.Join(tempDir, "debtik")
- netDir := filepath.Join(tempDir, "network")
- storeDir := filepath.Join(tempDir, "backups")
- _ = os.MkdirAll(debtikDir, 0755)
- _ = os.MkdirAll(netDir, 0755)
- origMetadata := []byte(`{"version": "orig"}`)
- origKey := []byte(`private-key-123`)
- origIfaces := []byte(`auto eth0`)
- _ = os.WriteFile(filepath.Join(debtikDir, "metadata.json"), origMetadata, 0644)
- _ = os.WriteFile(filepath.Join(debtikDir, "ssh_host_ed25519_key"), origKey, 0600)
- _ = os.WriteFile(filepath.Join(netDir, "interfaces.cfg"), origIfaces, 0644)
- restarted := false
- restartHook := func() error {
- restarted = true
- return nil
- }
- mgr := NewManager(debtikDir, netDir, storeDir, restartHook)
- _ = restarted
- // 1. Create backup
- zipBytes, _, err := mgr.CreateBackup("Pre-modify backup")
- if err != nil {
- t.Fatalf("CreateBackup failed: %v", err)
- }
- // 2. Modify live files
- _ = os.WriteFile(filepath.Join(debtikDir, "metadata.json"), []byte(`{"version": "modified"}`), 0644)
- _ = os.WriteFile(filepath.Join(debtikDir, "rogue.json"), []byte(`rogue data`), 0644)
- _ = os.WriteFile(filepath.Join(netDir, "interfaces.cfg"), []byte(`modified eth0`), 0644)
- // 3. Restore backup
- r := bytes.NewReader(zipBytes)
- res, err := mgr.RestoreBackup(r, int64(len(zipBytes)))
- if err != nil {
- t.Fatalf("RestoreBackup failed: %v", err)
- }
- if !res.Success {
- t.Fatal("Expected restore success true")
- }
- // 4. Verify original metadata was restored
- restoredMeta, err := os.ReadFile(filepath.Join(debtikDir, "metadata.json"))
- if err != nil {
- t.Fatalf("Failed reading restored metadata: %v", err)
- }
- if string(restoredMeta) != string(origMetadata) {
- t.Errorf("Metadata mismatch: got %q, want %q", string(restoredMeta), string(origMetadata))
- }
- // 5. Verify rogue file was removed during sync
- if _, err := os.Stat(filepath.Join(debtikDir, "rogue.json")); !os.IsNotExist(err) {
- t.Error("Expected rogue.json to be cleaned up after restore")
- }
- // 6. Verify key permissions are 0600
- keyInfo, err := os.Stat(filepath.Join(debtikDir, "ssh_host_ed25519_key"))
- if err != nil {
- t.Fatalf("Failed reading restored key: %v", err)
- }
- if keyInfo.Mode().Perm() != 0600 {
- t.Errorf("Expected 0600 perm on private key, got %v", keyInfo.Mode().Perm())
- }
- // 7. Verify pre-restore fallback backup exists in storeDir
- if res.SafetyBackup == "" {
- t.Error("Expected non-empty SafetyBackup path")
- }
- if _, err := os.Stat(res.SafetyBackup); err != nil {
- t.Errorf("Safety backup file does not exist: %v", err)
- }
- }
- func TestZipSlipPrevention(t *testing.T) {
- tempDir := t.TempDir()
- debtikDir := filepath.Join(tempDir, "debtik")
- netDir := filepath.Join(tempDir, "network")
- storeDir := filepath.Join(tempDir, "backups")
- mgr := NewManager(debtikDir, netDir, storeDir, nil)
- // Create malicious zip with traversal path
- buf := new(bytes.Buffer)
- zw := zip.NewWriter(buf)
- // Valid manifest
- mf, err := zw.Create("manifest.json")
- if err != nil {
- t.Fatal(err)
- }
- _, _ = mf.Write([]byte(`{"version": "1.0", "files": []}`))
- // Malicious traversal file
- malicious, err := zw.Create("../../../etc/evil.conf")
- if err != nil {
- t.Fatal(err)
- }
- _, _ = malicious.Write([]byte(`malicious payload`))
- _ = zw.Close()
- r := bytes.NewReader(buf.Bytes())
- _, err = mgr.InspectBackup(r, int64(buf.Len()))
- if err == nil {
- t.Fatal("Expected InspectBackup to fail on Zip Slip traversal entry")
- }
- if !strings.Contains(err.Error(), "illegal entry path") {
- t.Errorf("Expected illegal entry path error, got %v", err)
- }
- // Attempt restore
- _, err = mgr.RestoreBackup(r, int64(buf.Len()))
- if err == nil {
- t.Fatal("Expected RestoreBackup to fail on Zip Slip entry")
- }
- }
- func TestLocalBackupsAndPruning(t *testing.T) {
- tempDir := t.TempDir()
- debtikDir := filepath.Join(tempDir, "debtik")
- netDir := filepath.Join(tempDir, "network")
- storeDir := filepath.Join(tempDir, "backups")
- _ = os.MkdirAll(debtikDir, 0755)
- _ = os.WriteFile(filepath.Join(debtikDir, "dummy.json"), []byte("{}"), 0644)
- mgr := NewManager(debtikDir, netDir, storeDir, nil)
- // Create 7 pre-restore backups
- for i := 0; i < 7; i++ {
- _, err := mgr.CreatePreRestoreBackup()
- if err != nil {
- t.Fatalf("Failed creating pre-restore backup %d: %v", i, err)
- }
- }
- // List backups
- list, err := mgr.ListLocalBackups()
- if err != nil {
- t.Fatalf("ListLocalBackups failed: %v", err)
- }
- // Pre-restore backups should be pruned to keep 5
- if len(list) != 5 {
- t.Errorf("Expected 5 backups after pruning, got %d", len(list))
- }
- // Test GetLocalBackup
- savedFile := list[0].Filename
- path, err := mgr.GetLocalBackup(savedFile)
- if err != nil {
- t.Fatalf("GetLocalBackup failed: %v", err)
- }
- if _, err := os.Stat(path); err != nil {
- t.Fatalf("File does not exist: %v", err)
- }
- // Test GetLocalBackup traversal reject
- _, err = mgr.GetLocalBackup("../../evil.zip")
- if err == nil {
- t.Error("Expected error on traversal filename")
- }
- }
|