diff --git a/simplerisk-minimal/Dockerfile b/simplerisk-minimal/Dockerfile index ca2cba1..0a2aaea 100644 --- a/simplerisk-minimal/Dockerfile +++ b/simplerisk-minimal/Dockerfile @@ -3,22 +3,20 @@ ARG php_version=8.5 FROM alpine/curl:8.12.1 AS downloader -# PREGA_BUNDLE_FALLBACK is a CI-ONLY switch, default false. Pre-GA the prod -# bundle for a new version does not exist yet (it lands in public/bundles/ only -# at GA). CI sets this true so a pre-GA build can fall back to the testing -# bundle. A released image is ALWAYS built from the prod bundle and NEVER -# silently falls back to testing bytes (even on a transient prod failure). +# PREGA_BUNDLE_FALLBACK is a CI-ONLY switch, default false. Pre-GA the prod bundle +# for a new version does not exist yet (it lands in public/bundles/ only at GA). CI +# sets this true so a pre-GA build can fall back to the testing bundle WITHOUT hash +# verification (the release has no published hash yet). A released image is ALWAYS +# built from the VERIFIED prod bundle and NEVER from unverified testing bytes. ARG PREGA_BUNDLE_FALLBACK=false SHELL [ "/bin/ash", "-eo", "pipefail", "-c" ] -RUN mkdir -p /var/www && \ - if curl -fsSL https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-20260519-001.tgz -o /tmp/bundle.tgz; then true; \ - elif [ "$PREGA_BUNDLE_FALLBACK" = "true" ]; then \ - echo "prod bundle absent — pre-GA CI fallback to bundles-test"; \ - curl -fsSL https://bundles-test.simplerisk.com/simplerisk-20260519-001.tgz -o /tmp/bundle.tgz; \ - else echo "prod bundle simplerisk-20260519-001.tgz not found and PREGA_BUNDLE_FALLBACK=false" && exit 1; fi && \ - tar xzf /tmp/bundle.tgz -C /var/www && rm -f /tmp/bundle.tgz +# Download the prod bundle, verify its published sha256 (md5 fallback) from the +# updates feed, then extract -- fail-closed unless PREGA_BUNDLE_FALLBACK allows the +# pre-GA path. See common/download_and_verify_bundle.sh. +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN PREGA_BUNDLE_FALLBACK="$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh 20260519-001 FROM php:${php_version}-apache diff --git a/simplerisk-minimal/common/download_and_verify_bundle.sh b/simplerisk-minimal/common/download_and_verify_bundle.sh new file mode 100755 index 0000000..9a9691d --- /dev/null +++ b/simplerisk-minimal/common/download_and_verify_bundle.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# Download the released SimpleRisk bundle for the given version, verify it against +# the sha256 (md5 fallback) published in the production updates feed, then extract +# it into /var/www. Run from the alpine/curl downloader stage of the generated +# Dockerfile. +# +# Fail-closed by default: a RELEASED image (PREGA_BUNDLE_FALLBACK unset/false) is +# built ONLY from the prod bundle and ONLY if it matches its published hash -- a +# missing prod bundle, a missing feed hash, or a mismatch aborts the build, so a +# swapped S3 object can never be baked into a published image. The bundle (S3 +# public/bundles) and its hash (served updates feed) are stored independently. +# +# PRE-GA CI ONLY: before GA the prod bundle/hash for a new version do not exist +# yet (they land at GA). When PREGA_BUNDLE_FALLBACK=true AND the prod bundle is +# absent, fall back to the testing bundle WITHOUT verification (there is no +# published hash to check yet) and warn loudly. This path is never taken for a +# released image. +set -eu + +VERSION="${1:?usage: download_and_verify_bundle.sh }" +case "$VERSION" in + [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]) : ;; + *) echo "ERROR: bad version format: $VERSION" >&2; exit 1 ;; +esac + +FEED="https://updates.simplerisk.com/releases.xml" +PROD_URL="https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-${VERSION}.tgz" +TEST_URL="https://bundles-test.simplerisk.com/simplerisk-${VERSION}.tgz" +TGZ="/tmp/simplerisk-${VERSION}.tgz" + +if curl -fsSL -o "$TGZ" "$PROD_URL"; then + echo "Downloaded prod bundle for ${VERSION}; resolving published hash from ${FEED} ..." + ENTRY="$(curl -fsSL "$FEED" | sed -n "//,/<\/release>/p")" + EXPECTED="$(printf '%s\n' "$ENTRY" | grep -oE '[0-9a-f]{64}' | grep -oE '[0-9a-f]{64}' | head -1 || true)" + ALGO=sha256 + if [ -z "$EXPECTED" ]; then + EXPECTED="$(printf '%s\n' "$ENTRY" | grep -oE '[0-9a-f]{32}' | grep -oE '[0-9a-f]{32}' | head -1 || true)" + ALGO=md5 + fi + if [ -z "$EXPECTED" ]; then + echo "ERROR: no bundle_sha256 or bundle_md5 for ${VERSION} in ${FEED} -- refusing to extract an unverifiable prod bundle" >&2 + exit 1 + fi + ACTUAL="$(${ALGO}sum "$TGZ" | cut -d' ' -f1)" + if [ "$ACTUAL" != "$EXPECTED" ]; then + echo "ERROR: bundle ${ALGO} mismatch for ${VERSION} -- expected ${EXPECTED}, got ${ACTUAL}" >&2 + exit 1 + fi + echo "Bundle ${ALGO} verified (${ACTUAL})." +elif [ "${PREGA_BUNDLE_FALLBACK:-false}" = "true" ]; then + echo "WARNING: prod bundle simplerisk-${VERSION}.tgz absent -- PRE-GA CI fallback to bundles-test (UNVERIFIED: the release has no published hash yet)." >&2 + curl -fsSL -o "$TGZ" "$TEST_URL" +else + echo "ERROR: prod bundle simplerisk-${VERSION}.tgz not found and PREGA_BUNDLE_FALLBACK != true -- refusing to build a release from unverified bytes" >&2 + exit 1 +fi + +mkdir -p /var/www +tar xzf "$TGZ" -C /var/www +rm -f "$TGZ" +echo "Extracted bundle to /var/www." diff --git a/simplerisk-minimal/generate_dockerfile.sh b/simplerisk-minimal/generate_dockerfile.sh index 1171271..53ba282 100755 --- a/simplerisk-minimal/generate_dockerfile.sh +++ b/simplerisk-minimal/generate_dockerfile.sh @@ -34,22 +34,20 @@ if [ "$source_mode" == "download" ]; then cat << EOF >> "${SCRIPT_LOCATION}/Dockerfile" FROM alpine/curl:8.12.1 AS downloader -# PREGA_BUNDLE_FALLBACK is a CI-ONLY switch, default false. Pre-GA the prod -# bundle for a new version does not exist yet (it lands in public/bundles/ only -# at GA). CI sets this true so a pre-GA build can fall back to the testing -# bundle. A released image is ALWAYS built from the prod bundle and NEVER -# silently falls back to testing bytes (even on a transient prod failure). +# PREGA_BUNDLE_FALLBACK is a CI-ONLY switch, default false. Pre-GA the prod bundle +# for a new version does not exist yet (it lands in public/bundles/ only at GA). CI +# sets this true so a pre-GA build can fall back to the testing bundle WITHOUT hash +# verification (the release has no published hash yet). A released image is ALWAYS +# built from the VERIFIED prod bundle and NEVER from unverified testing bytes. ARG PREGA_BUNDLE_FALLBACK=false SHELL [ "/bin/ash", "-eo", "pipefail", "-c" ] -RUN mkdir -p /var/www && \\ - if curl -fsSL https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-$release.tgz -o /tmp/bundle.tgz; then true; \\ - elif [ "\$PREGA_BUNDLE_FALLBACK" = "true" ]; then \\ - echo "prod bundle absent — pre-GA CI fallback to bundles-test"; \\ - curl -fsSL https://bundles-test.simplerisk.com/simplerisk-$release.tgz -o /tmp/bundle.tgz; \\ - else echo "prod bundle simplerisk-$release.tgz not found and PREGA_BUNDLE_FALLBACK=false" && exit 1; fi && \\ - tar xzf /tmp/bundle.tgz -C /var/www && rm -f /tmp/bundle.tgz +# Download the prod bundle, verify its published sha256 (md5 fallback) from the +# updates feed, then extract -- fail-closed unless PREGA_BUNDLE_FALLBACK allows the +# pre-GA path. See common/download_and_verify_bundle.sh. +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN PREGA_BUNDLE_FALLBACK="\$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh $release EOF fi diff --git a/simplerisk/Dockerfile b/simplerisk/Dockerfile index 300327a..33b774b 100644 --- a/simplerisk/Dockerfile +++ b/simplerisk/Dockerfile @@ -4,11 +4,15 @@ ARG ubuntu_version_code=noble FROM alpine/curl:8.12.1 AS downloader ARG DB_LANG=en +# CI-ONLY pre-GA switch (default false) — see common/download_and_verify_bundle.sh. +ARG PREGA_BUNDLE_FALLBACK=false SHELL [ "/bin/ash", "-eo", "pipefail", "-c" ] -RUN mkdir -p /var/www && \ - curl -sL https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-20260519-001.tgz | tar xz -C /var/www && \ +# Download the prod bundle, verify its published sha256 (md5 fallback) from the +# updates feed, then extract (fail-closed) -- then fetch the release SQL schema. +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN PREGA_BUNDLE_FALLBACK="$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh 20260519-001 && \ curl -sL "https://github.com/simplerisk/database/raw/master/simplerisk-$DB_LANG-20260519-001.sql" > /simplerisk.sql # Using Ubuntu image diff --git a/simplerisk/common/download_and_verify_bundle.sh b/simplerisk/common/download_and_verify_bundle.sh new file mode 100755 index 0000000..9a9691d --- /dev/null +++ b/simplerisk/common/download_and_verify_bundle.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# Download the released SimpleRisk bundle for the given version, verify it against +# the sha256 (md5 fallback) published in the production updates feed, then extract +# it into /var/www. Run from the alpine/curl downloader stage of the generated +# Dockerfile. +# +# Fail-closed by default: a RELEASED image (PREGA_BUNDLE_FALLBACK unset/false) is +# built ONLY from the prod bundle and ONLY if it matches its published hash -- a +# missing prod bundle, a missing feed hash, or a mismatch aborts the build, so a +# swapped S3 object can never be baked into a published image. The bundle (S3 +# public/bundles) and its hash (served updates feed) are stored independently. +# +# PRE-GA CI ONLY: before GA the prod bundle/hash for a new version do not exist +# yet (they land at GA). When PREGA_BUNDLE_FALLBACK=true AND the prod bundle is +# absent, fall back to the testing bundle WITHOUT verification (there is no +# published hash to check yet) and warn loudly. This path is never taken for a +# released image. +set -eu + +VERSION="${1:?usage: download_and_verify_bundle.sh }" +case "$VERSION" in + [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]) : ;; + *) echo "ERROR: bad version format: $VERSION" >&2; exit 1 ;; +esac + +FEED="https://updates.simplerisk.com/releases.xml" +PROD_URL="https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-${VERSION}.tgz" +TEST_URL="https://bundles-test.simplerisk.com/simplerisk-${VERSION}.tgz" +TGZ="/tmp/simplerisk-${VERSION}.tgz" + +if curl -fsSL -o "$TGZ" "$PROD_URL"; then + echo "Downloaded prod bundle for ${VERSION}; resolving published hash from ${FEED} ..." + ENTRY="$(curl -fsSL "$FEED" | sed -n "//,/<\/release>/p")" + EXPECTED="$(printf '%s\n' "$ENTRY" | grep -oE '[0-9a-f]{64}' | grep -oE '[0-9a-f]{64}' | head -1 || true)" + ALGO=sha256 + if [ -z "$EXPECTED" ]; then + EXPECTED="$(printf '%s\n' "$ENTRY" | grep -oE '[0-9a-f]{32}' | grep -oE '[0-9a-f]{32}' | head -1 || true)" + ALGO=md5 + fi + if [ -z "$EXPECTED" ]; then + echo "ERROR: no bundle_sha256 or bundle_md5 for ${VERSION} in ${FEED} -- refusing to extract an unverifiable prod bundle" >&2 + exit 1 + fi + ACTUAL="$(${ALGO}sum "$TGZ" | cut -d' ' -f1)" + if [ "$ACTUAL" != "$EXPECTED" ]; then + echo "ERROR: bundle ${ALGO} mismatch for ${VERSION} -- expected ${EXPECTED}, got ${ACTUAL}" >&2 + exit 1 + fi + echo "Bundle ${ALGO} verified (${ACTUAL})." +elif [ "${PREGA_BUNDLE_FALLBACK:-false}" = "true" ]; then + echo "WARNING: prod bundle simplerisk-${VERSION}.tgz absent -- PRE-GA CI fallback to bundles-test (UNVERIFIED: the release has no published hash yet)." >&2 + curl -fsSL -o "$TGZ" "$TEST_URL" +else + echo "ERROR: prod bundle simplerisk-${VERSION}.tgz not found and PREGA_BUNDLE_FALLBACK != true -- refusing to build a release from unverified bytes" >&2 + exit 1 +fi + +mkdir -p /var/www +tar xzf "$TGZ" -C /var/www +rm -f "$TGZ" +echo "Extracted bundle to /var/www." diff --git a/simplerisk/generate_dockerfile.sh b/simplerisk/generate_dockerfile.sh index 7716e87..6ee759d 100755 --- a/simplerisk/generate_dockerfile.sh +++ b/simplerisk/generate_dockerfile.sh @@ -22,11 +22,15 @@ if [ "$release" != "testing" ]; then FROM alpine/curl:8.12.1 AS downloader ARG DB_LANG=en +# CI-ONLY pre-GA switch (default false) — see common/download_and_verify_bundle.sh. +ARG PREGA_BUNDLE_FALLBACK=false SHELL [ "/bin/ash", "-eo", "pipefail", "-c" ] -RUN mkdir -p /var/www && \\ - curl -sL https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-$release.tgz | tar xz -C /var/www && \\ +# Download the prod bundle, verify its published sha256 (md5 fallback) from the +# updates feed, then extract (fail-closed) -- then fetch the release SQL schema. +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN PREGA_BUNDLE_FALLBACK="\$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh $release && \\ curl -sL "https://github.com/simplerisk/database/raw/master/simplerisk-\$DB_LANG-$release.sql" > /simplerisk.sql EOF