Force fresh base-image metadata and package upgrades for supplemental images while retaining enforced vulnerability scans before publication. Co-Authored-By: Codex <noreply@openai.com> Signed-off-by: James Maes <james@kof22.com>
140 lines
5.2 KiB
Bash
140 lines
5.2 KiB
Bash
#!/usr/bin/env bash
|
|
# Exercise image scanning with local tool stubs; no Docker daemon or network needed.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
SRC_SCRIPTS="$(cd "${SCRIPT_DIR}/../src/scripts" && pwd)"
|
|
WORK=$(mktemp -d)
|
|
trap 'rm -rf "${WORK}"' EXIT
|
|
PASS=0
|
|
FAIL=0
|
|
pass() { echo "PASS"; PASS=$((PASS + 1)); }
|
|
fail() { echo "FAIL"; FAIL=$((FAIL + 1)); }
|
|
|
|
mkdir -p "${WORK}/bin" "${WORK}/home/bin" "${WORK}/repo"
|
|
cat > "${WORK}/bin/docker" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
printf 'docker %s\n' "$*" >> "${CALL_LOG}"
|
|
EOF
|
|
cat > "${WORK}/home/bin/trivy" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
[[ -f .trivyignore ]] || exit 99
|
|
printf 'trivy %s\n' "$*" >> "${CALL_LOG}"
|
|
# Redirect the production /tmp output path into the test workspace.
|
|
while [[ $# -gt 0 ]]; do
|
|
if [[ "$1" == --output ]]; then
|
|
printf '{"Results":[]}\n' > "${REPORT_DIR}/$(basename "$2")"
|
|
fi
|
|
shift
|
|
done
|
|
exit "${SCAN_EXIT:-0}"
|
|
EOF
|
|
chmod +x "${WORK}/bin/docker" "${WORK}/home/bin/trivy"
|
|
export PATH="${WORK}/bin:${PATH}" HOME="${WORK}/home"
|
|
export CALL_LOG="${WORK}/calls" REPORT_DIR="${WORK}"
|
|
export IMAGE_NAME=app REGISTRY=ghcr.io/example CIRCLE_SHA1=abcdef123456
|
|
export SUPPLEMENTAL_IMAGES_JSON='[{"name":"migrations","dockerfile":"Dockerfile.migrations"},{"name":"worker","base":"example/base:1"}]'
|
|
cd "${WORK}/repo"
|
|
touch .trivyignore
|
|
|
|
echo "=== PR image scan tests ==="
|
|
echo -n " TEST: feature metadata survives workspace restore and supplies Docker scan tag... "
|
|
export BASH_ENV="${WORK}/build-env" CIRCLE_BRANCH=feature/image-scan
|
|
export GITVERSION_MAJOR=1 GITVERSION_MINOR=2 GITVERSION_PATCH=3
|
|
export GITVERSION_SHA=abcdef123456
|
|
bash "${SRC_SCRIPTS}/export_version_vars.sh" > "${WORK}/version.log"
|
|
bash "${SRC_SCRIPTS}/persist_version_metadata.sh" >> "${WORK}/version.log"
|
|
export BASH_ENV="${WORK}/scan-env"
|
|
bash "${SRC_SCRIPTS}/restore_version_metadata.sh" >> "${WORK}/version.log"
|
|
bash "${SRC_SCRIPTS}/docker_build.sh" >> "${WORK}/version.log"
|
|
# shellcheck disable=SC1090
|
|
source "${BASH_ENV}"
|
|
export IMAGE_TAG='ghcr.io/example/app:${DOCKER_TAG}'
|
|
if bash "${SRC_SCRIPTS}/run_trivy_image.sh" >> "${WORK}/version.log" &&
|
|
grep -q 'trivy image .*--severity HIGH,CRITICAL --exit-code 1 ghcr.io/example/app:1.2.3-image-scan-abcdef123456-SNAPSHOT' "${CALL_LOG}"; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
|
|
echo -n " TEST: push=false refreshes both Dockerfile forms and scans without publishing... "
|
|
: > "${CALL_LOG}"
|
|
echo 'export DOCKER_ENV_TAG=develop' >> "${BASH_ENV}"
|
|
if PUSH_IMAGES=false bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
|
[[ $(grep -c '^docker build --pull --no-cache -f ' "${CALL_LOG}") == 2 ]] &&
|
|
[[ $(grep -c '^trivy image .*--severity HIGH,CRITICAL --exit-code 1 ' "${CALL_LOG}") == 2 ]] &&
|
|
! grep -Eq '^docker (push|login|tag|buildx) ' "${CALL_LOG}" &&
|
|
[[ -s "${WORK}/migrations.json" && -s "${WORK}/worker.json" ]]; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
|
|
echo -n " TEST: default push refreshes both Dockerfile forms and scans before publishing tags... "
|
|
: > "${CALL_LOG}"
|
|
unset PUSH_IMAGES
|
|
if bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
|
[[ $(grep -c '^docker build --pull --no-cache -f ' "${CALL_LOG}") == 2 ]] &&
|
|
[[ $(grep -c '^docker push ' "${CALL_LOG}") == 4 ]] &&
|
|
awk '
|
|
/^trivy image / { image=$NF; sub(/:[^:]+$/, "", image); scanned[image]=1 }
|
|
/^docker push / { image=$NF; sub(/:[^:]+$/, "", image); if (!scanned[image]) exit 1 }
|
|
' "${CALL_LOG}"; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
|
|
# Regression (0.3.5): CircleCI renders the boolean "push" parameter as "1"/"0" in
|
|
# the step environment, and release builds silently skipped the push.
|
|
for push in 1 true TRUE; do
|
|
echo -n " TEST: push=${push} (release builds) publishes every supplemental image... "
|
|
: > "${CALL_LOG}"
|
|
if PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
|
[[ $(grep -c '^docker push ' "${CALL_LOG}") == 4 ]] &&
|
|
grep -q '^docker push ghcr.io/example/app-migrations:' "${CALL_LOG}" &&
|
|
grep -q '^docker push ghcr.io/example/app-worker:' "${CALL_LOG}" &&
|
|
grep -q 'push=true' "${WORK}/supplemental.log"; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
done
|
|
|
|
for push in 0 false ""; do
|
|
echo -n " TEST: push='${push}' (PR scans) publishes nothing... "
|
|
: > "${CALL_LOG}"
|
|
if PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
|
! grep -Eq '^docker (push|tag) ' "${CALL_LOG}"; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
done
|
|
|
|
echo -n " TEST: an unrecognised push value fails instead of guessing... "
|
|
: > "${CALL_LOG}"
|
|
if PUSH_IMAGES=maybe bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" 2>&1; then
|
|
fail
|
|
elif ! grep -Eq '^docker (build|tag|push) ' "${CALL_LOG}" && grep -q "must be true/false" "${WORK}/supplemental.log"; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
|
|
for push in false true; do
|
|
echo -n " TEST: push=${push} fails on HIGH/CRITICAL findings before any push... "
|
|
: > "${CALL_LOG}"
|
|
rm -f "${WORK}/migrations.json"
|
|
if SCAN_EXIT=1 PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" 2>&1; then
|
|
fail
|
|
elif ! grep -q '^docker push ' "${CALL_LOG}" && [[ -s "${WORK}/migrations.json" ]]; then
|
|
pass
|
|
else
|
|
fail
|
|
fi
|
|
done
|
|
|
|
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
[[ ${FAIL} -eq 0 ]]
|