100 lines
3.4 KiB
Go
100 lines
3.4 KiB
Go
package tenantbackup
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/klarkxy/nekonest-cloud/relay/internal/tenantfs"
|
|
"github.com/klarkxy/nekonest-cloud/relay/internal/tenantstore"
|
|
"github.com/klarkxy/nekonest/relaycore/protocol"
|
|
)
|
|
|
|
func TestCreateVerifyAndRestoreTenantBackup(t *testing.T) {
|
|
const tenantID = "tenant_0123456789abcdef0123456789abcdef"
|
|
sourceRoot := t.TempDir()
|
|
paths, err := tenantfs.Resolve(sourceRoot, tenantID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := tenantstore.NewWithTransportMode(paths.Database, string(protocol.TransportSealed))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
token, err := store.RegisterDevice("host_0123456789abcdef0123456789abcdef", "Host", "linux")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
attachmentID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
if err := os.WriteFile(filepath.Join(paths.Attachments, attachmentID+".bin"), []byte("sealed payload"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(paths.Attachments, attachmentID+".json"), []byte(`{"id":"`+attachmentID+`"}`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := Create(context.Background(), sourceRoot, t.TempDir(), tenantID, 7, time.Date(2026, 8, 12, 12, 0, 0, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Verify(context.Background(), result.Path, tenantID, 7, result.ManifestSHA256); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resolved, err := ResolveReference(filepath.Dir(filepath.Dir(result.Path)), result.BackupRef); err != nil || resolved != result.Path {
|
|
t.Fatalf("backup reference did not resolve: %q err=%v", resolved, err)
|
|
}
|
|
targetRoot := t.TempDir()
|
|
restored, err := Restore(context.Background(), result.Path, targetRoot, tenantID, 7, result.ManifestSHA256)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if retried, err := Restore(context.Background(), result.Path, targetRoot, tenantID, 7, result.ManifestSHA256); err != nil || retried.Root != restored.Root {
|
|
t.Fatalf("idempotent restore failed: %#v err=%v", retried, err)
|
|
}
|
|
restoredStore, err := tenantstore.NewWithTransportMode(restored.Database, string(protocol.TransportSealed))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer restoredStore.Close()
|
|
if !restoredStore.ValidateDeviceToken("host_0123456789abcdef0123456789abcdef", token) {
|
|
t.Fatal("restored database lost the device credential")
|
|
}
|
|
payload, err := os.ReadFile(filepath.Join(restored.Attachments, attachmentID+".bin"))
|
|
if err != nil || string(payload) != "sealed payload" {
|
|
t.Fatalf("restored attachment = %q err=%v", payload, err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyRejectsTamperedBackup(t *testing.T) {
|
|
const tenantID = "tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
root := t.TempDir()
|
|
paths, err := tenantfs.Resolve(root, tenantID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := tenantstore.NewWithTransportMode(paths.Database, string(protocol.TransportSealed))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := Create(context.Background(), root, t.TempDir(), tenantID, 1, time.Now().UTC())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file, err := os.OpenFile(filepath.Join(result.Path, "relay.db"), os.O_WRONLY|os.O_APPEND, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, _ = file.Write([]byte("tamper"))
|
|
_ = file.Close()
|
|
if _, err := Verify(context.Background(), result.Path, tenantID, 1, result.ManifestSHA256); err == nil {
|
|
t.Fatal("tampered backup was accepted")
|
|
}
|
|
}
|