Public source validation / validate (push) Failing after 3m8s
85 lines
3.2 KiB
Bash
85 lines
3.2 KiB
Bash
#!/bin/sh
|
|
# Blocking pre-flight gate: fail if any deploy/*.Dockerfile pulls an
|
|
# external base image without an immutable `@sha256:` digest pin.
|
|
#
|
|
# Required by docs/operations/DEPLOYMENT_UNRAID.md §6 ("immutable
|
|
# release/image digests recorded") and
|
|
# docs/architecture/SECURITY_THREAT_MODEL.md §5 ("immutable image digest in
|
|
# production record"). See deploy/IMAGE_DIGESTS.md for the current ledger
|
|
# and the exact commands to resolve a real digest.
|
|
#
|
|
# This script intentionally does NOT contain any digest value itself. It
|
|
# only checks that Dockerfiles reference one. It must be run (and must pass)
|
|
# before any production image build/release; wire it into the release
|
|
# pipeline (e.g. `make build`, `make compose-up`, or CI) as a blocking step,
|
|
# alongside DEPLOYMENT_UNRAID.md §8 step 1 ("validate clean build and
|
|
# images").
|
|
#
|
|
# Usage: deploy/verify-image-digests.sh
|
|
# Exit status: 0 if every external base image is digest-pinned, 1 otherwise.
|
|
|
|
set -eu
|
|
|
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
status=0
|
|
|
|
for dockerfile in "$script_dir"/*.Dockerfile; do
|
|
[ -f "$dockerfile" ] || continue
|
|
|
|
# Stage names declared with `AS <name>` in this file: a later `FROM
|
|
# <name>` refers to a previous build stage, not a registry image, and
|
|
# must not be treated as something that needs a digest.
|
|
stage_names=$(grep -Eio '^FROM[[:space:]]+.*[[:space:]]AS[[:space:]]+[a-zA-Z0-9_.-]+' "$dockerfile" \
|
|
| awk '{print tolower($NF)}' || true)
|
|
|
|
grep -Ein '^FROM[[:space:]]' "$dockerfile" | while IFS= read -r line; do
|
|
lineno=${line%%:*}
|
|
rest=${line#*:}
|
|
# First token after FROM, ignoring an optional --platform=... flag.
|
|
image=$(printf '%s\n' "$rest" | awk '{
|
|
for (i = 1; i <= NF; i++) {
|
|
if (tolower($i) == "from") continue
|
|
if ($i ~ /^--platform=/) continue
|
|
print $i
|
|
break
|
|
}
|
|
}')
|
|
image_lc=$(printf '%s' "$image" | tr 'A-Z' 'a-z')
|
|
|
|
is_stage=0
|
|
for s in $stage_names; do
|
|
if [ "$s" = "$image_lc" ]; then
|
|
is_stage=1
|
|
break
|
|
fi
|
|
done
|
|
[ "$is_stage" -eq 1 ] && continue
|
|
|
|
case "$image_lc" in
|
|
scratch) : ;; # Docker's built-in empty rootfs; no registry manifest or digest exists.
|
|
*@sha256:*) : ;; # pinned, OK
|
|
*)
|
|
echo "UNPINNED: $(basename "$dockerfile"):$lineno: FROM $image" >&2
|
|
echo " action: resolve a real digest (see deploy/IMAGE_DIGESTS.md) and" >&2
|
|
echo " change this line to 'FROM $image@sha256:<digest>'." >&2
|
|
echo "unpinned" >> "$script_dir/.verify-image-digests.tmp"
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
|
|
if [ -f "$script_dir/.verify-image-digests.tmp" ]; then
|
|
rm -f "$script_dir/.verify-image-digests.tmp"
|
|
status=1
|
|
fi
|
|
|
|
if [ "$status" -ne 0 ]; then
|
|
echo >&2
|
|
echo "deploy/verify-image-digests.sh: FAILED - one or more base images are not pinned by digest." >&2
|
|
echo "This blocks release per DEPLOYMENT_UNRAID.md §6 / SECURITY_THREAT_MODEL.md §5." >&2
|
|
else
|
|
echo "deploy/verify-image-digests.sh: OK - all external base images are digest-pinned."
|
|
fi
|
|
|
|
exit "$status"
|