diff --git a/simplerisk-minimal/Dockerfile b/simplerisk-minimal/Dockerfile index b62dd3c..c60ca87 100644 --- a/simplerisk-minimal/Dockerfile +++ b/simplerisk-minimal/Dockerfile @@ -5,8 +5,8 @@ FROM alpine/curl:8.12.1 AS downloader 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 +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN 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..1f31264 --- /dev/null +++ b/simplerisk-minimal/common/download_and_verify_bundle.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Download the released SimpleRisk bundle for the given version and verify it +# against the sha256 (or md5 fallback) published in the production updates feed +# before extracting into /var/www. Fail-closed: the build aborts if the bundle +# does not match its published hash, or if the feed publishes no hash for the +# version. Run from the alpine/curl downloader stage of the generated Dockerfile. +# +# The bundle (S3 public/bundles) and the hash (served updates feed) are +# independently stored, so a swapped S3 object fails the build instead of being +# baked into a published 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" +BUNDLE_URL="https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-${VERSION}.tgz" +TGZ="/tmp/simplerisk-${VERSION}.tgz" + +echo "Downloading bundle for ${VERSION} ..." +curl -fsSL -o "$TGZ" "$BUNDLE_URL" + +echo "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 the updates feed -- refusing to extract an unverifiable 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})." + +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 433fd1a..9a11c45 100755 --- a/simplerisk-minimal/generate_dockerfile.sh +++ b/simplerisk-minimal/generate_dockerfile.sh @@ -13,6 +13,11 @@ else echo "No release version provided. Aborting." && exit 1 fi +case "$release" in + testing|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]) : ;; + *) echo "Invalid release '$release' (expected YYYYMMDD-NNN or 'testing'). Aborting." && exit 1 ;; +esac + cat << EOF > "${SCRIPT_LOCATION}/Dockerfile" # Dockerfile generated by script ARG php_version=8.4 @@ -25,8 +30,8 @@ FROM alpine/curl:8.12.1 AS downloader 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 +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN sh /download_and_verify_bundle.sh $release EOF fi diff --git a/simplerisk/Dockerfile b/simplerisk/Dockerfile index c5e6a0a..b81354f 100644 --- a/simplerisk/Dockerfile +++ b/simplerisk/Dockerfile @@ -7,8 +7,8 @@ ARG DB_LANG=en 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 && \ +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN 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..1f31264 --- /dev/null +++ b/simplerisk/common/download_and_verify_bundle.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Download the released SimpleRisk bundle for the given version and verify it +# against the sha256 (or md5 fallback) published in the production updates feed +# before extracting into /var/www. Fail-closed: the build aborts if the bundle +# does not match its published hash, or if the feed publishes no hash for the +# version. Run from the alpine/curl downloader stage of the generated Dockerfile. +# +# The bundle (S3 public/bundles) and the hash (served updates feed) are +# independently stored, so a swapped S3 object fails the build instead of being +# baked into a published 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" +BUNDLE_URL="https://simplerisk-downloads.s3.amazonaws.com/public/bundles/simplerisk-${VERSION}.tgz" +TGZ="/tmp/simplerisk-${VERSION}.tgz" + +echo "Downloading bundle for ${VERSION} ..." +curl -fsSL -o "$TGZ" "$BUNDLE_URL" + +echo "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 the updates feed -- refusing to extract an unverifiable 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})." + +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 8a9c214..d955ff5 100755 --- a/simplerisk/generate_dockerfile.sh +++ b/simplerisk/generate_dockerfile.sh @@ -11,6 +11,11 @@ else echo "No release version provided. Aborting." && exit 1 fi +case "$release" in + testing|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]) : ;; + *) echo "Invalid release '$release' (expected YYYYMMDD-NNN or 'testing'). Aborting." && exit 1 ;; +esac + cat << EOF > "${SCRIPT_LOCATION}/Dockerfile" # Dockerfile generated by script ARG ubuntu_version_code=noble @@ -25,8 +30,8 @@ ARG DB_LANG=en 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 && \\ +COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh +RUN sh /download_and_verify_bundle.sh $release && \\ curl -sL "https://github.com/simplerisk/database/raw/master/simplerisk-\$DB_LANG-$release.sql" > /simplerisk.sql EOF