Files
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

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"