diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e351d37..e056f59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,17 @@ jobs: exit 1 fi + - name: Verify exact-digest policy + run: | + digest="sha256:$(printf '%064d' 0)" + test "$(sh scripts/verify-exact-digest-runtime.sh ghcr.io/ploos-as/soju "$digest")" = "ghcr.io/ploos-as/soju@$digest" + for invalid in latest v0.1.0 sha256:deadbeef "sha256:$(printf '%064d' 0)X"; do + if sh scripts/verify-exact-digest-runtime.sh ghcr.io/ploos-as/soju "$invalid"; then + echo "invalid digest unexpectedly accepted: $invalid" >&2 + exit 1 + fi + done + - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e934de2..b16242d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,6 +69,41 @@ jobs: provenance: mode=max sbom: true + - name: Qualify published image by exact digest + env: + IMAGE_DIGEST: ${{ steps.push.outputs.digest }} + run: | + set -eu + image_ref="$(sh scripts/verify-exact-digest-runtime.sh "$IMAGE_NAME" "$IMAGE_DIGEST")" + docker pull "$image_ref" + resolved="$(docker image inspect "$image_ref" --format '{{index .RepoDigests 0}}')" + test "$resolved" = "$image_ref" + revision="$(docker image inspect "$image_ref" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" + version="$(docker image inspect "$image_ref" --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" + test "$revision" = "$GITHUB_SHA" + test "$version" = "$GITHUB_REF_NAME" + test "$(docker run --rm --entrypoint id "$image_ref" -u)" = "1000" + test "$(docker run --rm --entrypoint id "$image_ref" -g)" = "1000" + mkdir -p data-release run-release + sudo chown 1000:1000 data-release run-release + cid=$(docker run -d --rm \ + -v "$PWD/data-release:/var/lib/soju" \ + -v "$PWD/run-release:/run/soju" \ + "$image_ref") + trap 'docker logs "$cid" 2>&1 || true; docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT + for i in $(seq 1 30); do + if docker ps --filter "id=$cid" --format '{{.ID}}' | grep -q . \ + && test -f data-release/main.db \ + && test -S run-release/admin; then + qualified=true + break + fi + sleep 1 + done + test "${qualified:-false}" = true + docker rm -f "$cid" + trap - EXIT + - name: Attest image uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a with: diff --git a/scripts/verify-exact-digest-runtime.sh b/scripts/verify-exact-digest-runtime.sh new file mode 100644 index 0000000..e354c42 --- /dev/null +++ b/scripts/verify-exact-digest-runtime.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu + +image_name=${1:-ghcr.io/ploos-as/soju} +digest=${2:-} + +case "$digest" in + sha256:[0-9a-f][0-9a-f]*) ;; + *) echo "invalid digest: $digest" >&2; exit 1 ;; +esac + +if [ "${#digest}" -ne 71 ]; then + echo "digest must be sha256 plus exactly 64 lowercase hex characters" >&2 + exit 1 +fi + +hex=${digest#sha256:} +case "$hex" in + *[!0-9a-f]*) echo "digest contains non-hex characters" >&2; exit 1 ;; +esac + +printf '%s@%s\n' "$image_name" "$digest"