166 lines
5.3 KiB
Go
166 lines
5.3 KiB
Go
//go:build windows
|
|
|
|
package backup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
|
)
|
|
|
|
func TestStageArchiveProtectsWindowsStagingArtifactsWithOwnerOnlyACLs(t *testing.T) {
|
|
installation := preflightTestInstallation(t)
|
|
if err := os.MkdirAll(installation.ControlDirectory(), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
archive := filepath.Join(t.TempDir(), "valid.zip")
|
|
writePreflightArchive(t, archive, preflightArchiveSpec{
|
|
entries: []preflightArchiveEntry{{path: "configuration/operator.env", body: []byte("safe")}},
|
|
})
|
|
result, err := Preflight(context.Background(), installation, PreflightRequest{Archive: archive, Confirm: true}, permissivePreflightDependencies())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer result.CloseArchive()
|
|
|
|
staged, err := result.StageArchive(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer staged.Close()
|
|
if err := staged.parent.Validate(); err != nil {
|
|
t.Fatalf("retained staging root ACL = %v, want owner-only", err)
|
|
}
|
|
if err := staged.file.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
staged.file = nil
|
|
contents, found, err := staged.parent.ReadRegular(staged.name, 1<<20)
|
|
if err != nil || !found || len(contents) == 0 {
|
|
t.Fatalf("retained staged archive read = found:%t bytes:%d error:%v, want owner-only regular", found, len(contents), err)
|
|
}
|
|
}
|
|
|
|
func TestStageArchiveRetainsTwoFilesThroughOneWindowsRootCapability(t *testing.T) {
|
|
installation := preflightTestInstallation(t)
|
|
archives := []string{filepath.Join(t.TempDir(), "candidate.zip"), filepath.Join(t.TempDir(), "recovery.zip")}
|
|
for _, archive := range archives {
|
|
writePreflightArchive(t, archive, preflightArchiveSpec{
|
|
entries: []preflightArchiveEntry{{path: "configuration/operator.env", body: []byte("safe")}},
|
|
})
|
|
}
|
|
candidate, err := Preflight(context.Background(), installation, PreflightRequest{Archive: archives[0], Confirm: true}, permissivePreflightDependencies())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer candidate.CloseArchive()
|
|
recovery, err := Preflight(context.Background(), installation, PreflightRequest{Archive: archives[1], Confirm: true}, permissivePreflightDependencies())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer recovery.CloseArchive()
|
|
|
|
candidateStage, err := candidate.StageArchive(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
recoveryStage, err := recovery.stageArchiveAlongside(context.Background(), candidateStage)
|
|
if err != nil {
|
|
_ = candidateStage.Close()
|
|
t.Fatal(err)
|
|
}
|
|
if candidateStage.parent != recoveryStage.parent {
|
|
t.Fatal("paired stages did not share the retained root capability")
|
|
}
|
|
if err := candidateStage.Close(); err != nil {
|
|
_ = recoveryStage.Close()
|
|
t.Fatal(err)
|
|
}
|
|
if err := recoveryStage.file.Close(); err != nil {
|
|
_ = recoveryStage.Close()
|
|
t.Fatal(err)
|
|
}
|
|
recoveryStage.file = nil
|
|
if _, found, err := recoveryStage.parent.ReadRegular(recoveryStage.name, 1<<20); err != nil || !found {
|
|
_ = recoveryStage.Close()
|
|
t.Fatalf("recovery stage after candidate cleanup = found:%t err:%v", found, err)
|
|
}
|
|
if err := recoveryStage.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestStageArchiveCloseUsesPinnedRootAfterAncestorSwap(t *testing.T) {
|
|
installation := preflightTestInstallation(t)
|
|
if err := os.MkdirAll(installation.ControlDirectory(), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
archive := filepath.Join(t.TempDir(), "valid.zip")
|
|
writePreflightArchive(t, archive, preflightArchiveSpec{
|
|
entries: []preflightArchiveEntry{{path: "configuration/operator.env", body: []byte("safe")}},
|
|
})
|
|
result, err := Preflight(context.Background(), installation, PreflightRequest{Archive: archive, Confirm: true}, permissivePreflightDependencies())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer result.CloseArchive()
|
|
|
|
outsideParent, err := filepath.EvalSymlinks(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outside := filepath.Join(outsideParent, "outside")
|
|
if err := os.Mkdir(outside, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := safeio.ProtectPrivateDirectory(outside); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sentinelPath := filepath.Join(outside, "sentinel")
|
|
sentinel := []byte("outside sentinel")
|
|
if err := os.WriteFile(sentinelPath, sentinel, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
attemptedSwap := false
|
|
restoreHook := safeio.SetPrivateDirectoryTestHookForTest(func(stage string) {
|
|
if stage != "before-stage-archive-remove" || attemptedSwap {
|
|
return
|
|
}
|
|
attemptedSwap = true
|
|
if err := os.Rename(result.stagingRoot, result.stagingRoot+"-moved"); err == nil {
|
|
t.Fatal("staging-root rename succeeded while Close retained its directory handle")
|
|
}
|
|
})
|
|
defer restoreHook()
|
|
|
|
staged, err := result.StageArchive(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stagedName := staged.name
|
|
if filepath.Dir(staged.path) != result.stagingRoot {
|
|
t.Fatalf("staged archive directory = %q, want %q", filepath.Dir(staged.path), result.stagingRoot)
|
|
}
|
|
if err := staged.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !attemptedSwap {
|
|
t.Fatal("stage archive cleanup hook did not run")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(result.stagingRoot, stagedName)); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("staging archive = %v, want os.ErrNotExist", err)
|
|
}
|
|
gotSentinel, err := os.ReadFile(sentinelPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(gotSentinel) != string(sentinel) {
|
|
t.Fatalf("outside sentinel = %q, want %q", gotSentinel, sentinel)
|
|
}
|
|
}
|