Files
felhom-controller/controller/cmd/controller/r1105_dockerfile_restic_test.go
T

87 lines
3.5 KiB
Go

package main
import (
"os"
"regexp"
"strings"
"testing"
)
// R-1105: restic is pinned OUTSIDE the OS. Until v0.323.0 it came from the runtime's apt list, so the
// version was „whatever Debian ships" — a Debian 12 → 13 base move would have swapped restic 0.14.0 for
// 0.18.0 under the off-site code with nobody choosing it. The consequences pinned here:
// - the version is one explicit ARG, and each architecture's checksum is pinned beside it;
// - the fetch checks restic's signature (vendored key, no keyserver) AND both checksums;
// - the runtime takes restic ONLY from that stage — no apt package can bring a second one;
// - every base image is pinned by digest, and the Docker repo's codename is read from the image.
func TestR1105_ResticIsPinnedOutsideTheOS(t *testing.T) {
raw, err := os.ReadFile("../../Dockerfile")
if err != nil {
t.Fatal(err)
}
src := string(raw)
// Stages, split on FROM, with `\`-continued lines joined so an apt list reads as one command.
joined := strings.ReplaceAll(src, "\\\n", " ")
var stages []string
for _, part := range regexp.MustCompile(`(?m)^FROM `).Split(joined, -1)[1:] {
stages = append(stages, "FROM "+part)
}
if len(stages) < 3 {
t.Fatalf("expected a builder, a restic and a runtime stage, found %d stages", len(stages))
}
var fetch string
for _, s := range stages {
if regexp.MustCompile(`^FROM \S+ AS restic\b`).MatchString(s) {
fetch = s
}
}
if fetch == "" {
t.Fatal("no `AS restic` stage: restic is no longer fetched as a pinned release binary")
}
runtime := stages[len(stages)-1]
if m := regexp.MustCompile(`ARG RESTIC_VERSION=(\d+\.\d+\.\d+)\s`).FindStringSubmatch(fetch); m == nil {
t.Error("ARG RESTIC_VERSION=X.Y.Z is missing — the version must be an explicit decision")
}
for _, arch := range []string{"AMD64", "ARM64"} {
if !regexp.MustCompile(`ARG RESTIC_SHA256_` + arch + `=[0-9a-f]{64}\s`).MatchString(fetch) {
t.Errorf("no pinned sha256 for %s", arch)
}
}
for _, want := range []string{
"COPY third_party/restic/restic-release-key.asc",
"gpgv --keyring", // SHA256SUMS is restic's, signed
`SHA256SUMS | sha256sum -c -`, // the download matches the published sum
`echo "${want} ${f}" | sha256sum -c -`, // ...and the sum pinned in this file
} {
if !strings.Contains(fetch, want) {
t.Errorf("the restic stage no longer does %q", want)
}
}
if _, err := os.Stat("../../third_party/restic/restic-release-key.asc"); err != nil {
t.Errorf("the vendored release key is missing: %v", err)
}
if !strings.Contains(runtime, "COPY --from=restic /usr/local/bin/restic /usr/local/bin/restic") {
t.Error("the runtime does not take restic from the pinned stage")
}
aptRe := regexp.MustCompile(`apt-get install[^&]*`)
for _, inst := range aptRe.FindAllString(runtime, -1) {
if regexp.MustCompile(`\srestic(\s|$)`).MatchString(inst) {
t.Errorf("the runtime's apt list installs restic — a second, OS-chosen restic: %q", strings.Join(strings.Fields(inst), " "))
}
}
for _, from := range regexp.MustCompile(`(?m)^FROM (\S+)`).FindAllStringSubmatch(src, -1) {
if !strings.Contains(from[1], "@sha256:") {
t.Errorf("base image %s is not pinned by digest", from[1])
}
}
if regexp.MustCompile(`download\.docker\.com/linux/debian (bookworm|trixie|forky)`).MatchString(src) {
t.Error("the Docker apt repo codename is hard-coded — it must come from /etc/os-release (VERSION_CODENAME)")
}
if !strings.Contains(runtime, "${VERSION_CODENAME}") {
t.Error("the Docker apt repo line does not read VERSION_CODENAME")
}
}