feat: add deployment foundation and cross-device handoff
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package aptrepo
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"server-deploy/internal/installplan"
|
||||
)
|
||||
|
||||
// VerifyArtifacts authenticates repository metadata anew before checking the
|
||||
// selected five deb files. It never accepts a caller-asserted authenticated lock.
|
||||
// The staging directories and ancestors must be trusted and not concurrently
|
||||
// modified. This is observation evidence, not a capability to later execute paths.
|
||||
func VerifyArtifacts(metadataDirectory, artifactDirectory, suite, arch string, versions map[string]string, now time.Time) (Result, error) {
|
||||
result, err := Verify(metadataDirectory, suite, arch, versions, now)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
if err := verifyArtifacts(artifactDirectory, result.Lock); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
result.PackageBytesVerified = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func verifyArtifacts(directory string, lock installplan.Lock) error {
|
||||
reject := errors.New("invalid staged Docker artifacts")
|
||||
if _, err := installplan.Validate(lock, lock.Suite, lock.Architecture); err != nil {
|
||||
return reject
|
||||
}
|
||||
if !filepath.IsAbs(directory) {
|
||||
return reject
|
||||
}
|
||||
directory = filepath.Clean(directory)
|
||||
before, err := os.Lstat(directory)
|
||||
if err != nil || !before.IsDir() || before.Mode()&os.ModeSymlink != 0 {
|
||||
return reject
|
||||
}
|
||||
root, err := os.OpenRoot(directory)
|
||||
if err != nil {
|
||||
return reject
|
||||
}
|
||||
defer root.Close()
|
||||
opened, err := root.Stat(".")
|
||||
if err != nil || !os.SameFile(before, opened) {
|
||||
return reject
|
||||
}
|
||||
expected := make(map[string]installplan.Package, len(lock.Packages))
|
||||
for _, p := range lock.Packages {
|
||||
expected[path.Base(p.Filename)] = p
|
||||
}
|
||||
dir, err := root.Open(".")
|
||||
if err != nil {
|
||||
return reject
|
||||
}
|
||||
defer dir.Close()
|
||||
// Enumerate at most the expected count plus one, never an unbounded directory.
|
||||
seen := make(map[string]bool)
|
||||
for {
|
||||
entries, err := dir.ReadDir(1)
|
||||
if err != nil && err != io.EOF {
|
||||
return reject
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return reject
|
||||
}
|
||||
name := entries[0].Name()
|
||||
p, ok := expected[name]
|
||||
if !ok || seen[name] {
|
||||
return reject
|
||||
}
|
||||
seen[name] = true
|
||||
if err := verifyArtifact(root, name, p); err != nil {
|
||||
return reject
|
||||
}
|
||||
}
|
||||
if len(seen) != len(expected) {
|
||||
return reject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyArtifact(root *os.Root, name string, p installplan.Package) error {
|
||||
reject := errors.New("artifact size or digest mismatch")
|
||||
before, err := root.Lstat(name)
|
||||
if err != nil || !before.Mode().IsRegular() || before.Size() != int64(p.Size) {
|
||||
return reject
|
||||
}
|
||||
file, err := root.Open(name)
|
||||
if err != nil {
|
||||
return reject
|
||||
}
|
||||
defer file.Close()
|
||||
opened, err := file.Stat()
|
||||
if err != nil || !opened.Mode().IsRegular() || !os.SameFile(before, opened) || opened.Size() != before.Size() {
|
||||
return reject
|
||||
}
|
||||
digest := sha256.New()
|
||||
n, err := io.CopyBuffer(digest, io.LimitReader(file, int64(p.Size)+1), make([]byte, 64<<10))
|
||||
if err != nil || n != int64(p.Size) || "sha256:"+hex.EncodeToString(digest.Sum(nil)) != p.Digest {
|
||||
return reject
|
||||
}
|
||||
after, err := file.Stat()
|
||||
if err != nil || after.Size() != opened.Size() || !after.ModTime().Equal(opened.ModTime()) {
|
||||
return reject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user