118 lines
3.2 KiB
Go
118 lines
3.2 KiB
Go
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
|
|
}
|