fix(backup): retain staging cleanup capability
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@@ -25,6 +26,14 @@ import (
|
||||
|
||||
var archiveDrivePath = regexp.MustCompile(`^[A-Za-z]:/`)
|
||||
|
||||
func newStagingArchiveName() (string, error) {
|
||||
value := make([]byte, 16)
|
||||
if _, err := rand.Read(value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "archive-" + hex.EncodeToString(value) + ".zip", nil
|
||||
}
|
||||
|
||||
// PreflightRequest identifies an archive and the explicit protections required to inspect a
|
||||
// restore that contains external secret payloads. Preflight never writes to the installation.
|
||||
type PreflightRequest struct {
|
||||
@@ -97,9 +106,10 @@ type verifiedArchive struct {
|
||||
// stagedArchive holds an installation-private, immutable copy of the exact bytes accepted by
|
||||
// Preflight. The original archive remains retained only for provenance revalidation.
|
||||
type stagedArchive struct {
|
||||
file *os.File
|
||||
path string
|
||||
directory string
|
||||
file *os.File
|
||||
parent safeio.PrivateDirectoryHandle
|
||||
name string
|
||||
path string
|
||||
}
|
||||
|
||||
type inspectedArchiveEntry struct {
|
||||
@@ -278,21 +288,44 @@ func (result PreflightResult) StageArchive(ctx context.Context) (_ *stagedArchiv
|
||||
if result.ArchiveSize < 0 || freeBytes < uint64(result.ArchiveSize) {
|
||||
return nil, errors.New("insufficient free disk space for private restore staging archive")
|
||||
}
|
||||
directory, err := os.MkdirTemp(result.stagingRoot, "archive-")
|
||||
if err != nil {
|
||||
return nil, errors.New("create private restore staging directory")
|
||||
parent, found, err := safeio.OpenPrivateDirectory(result.stagingRoot, true)
|
||||
if err != nil || !found {
|
||||
if parent != nil {
|
||||
_ = parent.Close()
|
||||
}
|
||||
return nil, errors.New("open private restore staging root")
|
||||
}
|
||||
if err := safeio.ProtectPrivateDirectory(directory); err != nil {
|
||||
_ = os.Remove(directory)
|
||||
return nil, errors.New("protect private restore staging directory")
|
||||
var (
|
||||
file *os.File
|
||||
name string
|
||||
)
|
||||
for attempt := 0; attempt < 8; attempt++ {
|
||||
name, err = newStagingArchiveName()
|
||||
if err != nil {
|
||||
_ = parent.Close()
|
||||
return nil, errors.New("create private restore staging archive")
|
||||
}
|
||||
var created bool
|
||||
file, created, err = parent.CreateRegularFile(name)
|
||||
if err != nil {
|
||||
_ = parent.Close()
|
||||
return nil, errors.New("create private restore staging archive")
|
||||
}
|
||||
if created {
|
||||
break
|
||||
}
|
||||
file = nil
|
||||
}
|
||||
path := filepath.Join(directory, "archive.zip")
|
||||
file, err := safeio.CreateCanonicalNewPrivateFile(path)
|
||||
if err != nil {
|
||||
_ = os.Remove(directory)
|
||||
if file == nil {
|
||||
_ = parent.Close()
|
||||
return nil, errors.New("create private restore staging archive")
|
||||
}
|
||||
staged := &stagedArchive{file: file, path: path, directory: directory}
|
||||
staged := &stagedArchive{
|
||||
file: file,
|
||||
parent: parent,
|
||||
name: name,
|
||||
path: filepath.Join(result.stagingRoot, name),
|
||||
}
|
||||
completed := false
|
||||
defer func() {
|
||||
if !completed {
|
||||
@@ -343,7 +376,7 @@ func (result PreflightResult) StageArchive(ctx context.Context) (_ *stagedArchiv
|
||||
return staged, nil
|
||||
}
|
||||
|
||||
// Close removes only the staging file and directory created by StageArchive.
|
||||
// Close removes only the staging file created by StageArchive through its retained directory.
|
||||
func (staged *stagedArchive) Close() error {
|
||||
if staged == nil {
|
||||
return nil
|
||||
@@ -355,18 +388,19 @@ func (staged *stagedArchive) Close() error {
|
||||
}
|
||||
staged.file = nil
|
||||
}
|
||||
if staged.path != "" {
|
||||
if err := os.Remove(staged.path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
if staged.parent != nil {
|
||||
safeio.NotifyPrivateDirectoryTestHookForTest("before-stage-archive-remove")
|
||||
removed, err := staged.parent.RemoveRegular(staged.name)
|
||||
if err != nil || !removed {
|
||||
failed = true
|
||||
}
|
||||
staged.path = ""
|
||||
}
|
||||
if staged.directory != "" {
|
||||
if err := os.Remove(staged.directory); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
if err := staged.parent.Close(); err != nil {
|
||||
failed = true
|
||||
}
|
||||
staged.directory = ""
|
||||
staged.parent = nil
|
||||
}
|
||||
staged.name = ""
|
||||
staged.path = ""
|
||||
if failed {
|
||||
return errors.New("destroy private restore staging archive")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user