From 649167da12ecde9b1779c4c5df6f16ddfb7e124e Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:08:57 +0200 Subject: [PATCH 1/7] M0.13: make release verification runtime-neutral --- scripts/verify-release.sh | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index 9ff4ebb..89a2c07 100644 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh @@ -5,6 +5,7 @@ IMAGE="${IMAGE:-ghcr.io/ploos-as/glowing-bear}" VERSION="${VERSION:?VERSION is required}" OIDC_ISSUER="${OIDC_ISSUER:-https://token.actions.githubusercontent.com}" PULL="${PULL:-0}" +RUNTIME="${RUNTIME:-auto}" if [[ "$VERSION" == v* ]]; then TAG="$VERSION" @@ -23,7 +24,15 @@ if [[ "$PULL" != "0" && "$PULL" != "1" ]]; then exit 1 fi -for command in docker cosign; do +case "$RUNTIME" in + auto|docker|podman) ;; + *) + echo "RUNTIME must be auto, docker, or podman" >&2 + exit 1 + ;; +esac + +for command in skopeo cosign; do if ! command -v "$command" >/dev/null 2>&1; then echo "required command not found: $command" >&2 exit 1 @@ -33,11 +42,7 @@ done primary="$IMAGE:$VERSION" identity="https://github.com/Ploos-AS/glowing-bear/.github/workflows/sign-release.yml@refs/tags/$TAG" -# Capture the complete Buildx output before parsing it. Piping imagetools inspect -# directly into an awk program that exits after the Digest line can close the -# pipe early; with pipefail enabled Buildx may then report SIGPIPE as exit 255. -inspect_output="$(docker buildx imagetools inspect "$primary")" -image_digest="$(awk '/^Digest:/ {print $2; found=1} END {if (!found) exit 1}' <<<"$inspect_output")" +image_digest="$(skopeo inspect --no-tags --format '{{.Digest}}' "docker://$primary")" if [[ ! "$image_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then echo "could not resolve an immutable sha256 digest for $primary" >&2 exit 1 @@ -58,8 +63,25 @@ cosign verify \ echo "Signature verification passed for $signed_ref" >&2 if [[ "$PULL" == "1" ]]; then - docker pull "$signed_ref" >&2 - echo "Pulled verified immutable image: $signed_ref" >&2 + selected_runtime="$RUNTIME" + if [[ "$selected_runtime" == "auto" ]]; then + if command -v docker >/dev/null 2>&1; then + selected_runtime="docker" + elif command -v podman >/dev/null 2>&1; then + selected_runtime="podman" + else + echo "PULL=1 requires docker or podman" >&2 + exit 1 + fi + fi + + if ! command -v "$selected_runtime" >/dev/null 2>&1; then + echo "required runtime not found: $selected_runtime" >&2 + exit 1 + fi + + "$selected_runtime" pull "$signed_ref" >&2 + echo "Pulled verified immutable image with $selected_runtime: $signed_ref" >&2 fi printf '%s\n' "$signed_ref" From 670705e578abfeff4efe37acfa42c22ccfe22d28 Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:09:05 +0200 Subject: [PATCH 2/7] M0.13: use neutral verifier from Compose gate --- scripts/deploy-verified-compose.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-verified-compose.sh b/scripts/deploy-verified-compose.sh index b8da85e..513e935 100644 --- a/scripts/deploy-verified-compose.sh +++ b/scripts/deploy-verified-compose.sh @@ -4,7 +4,7 @@ set -euo pipefail VERSION="${VERSION:?VERSION is required}" COMPOSE_FILE="${COMPOSE_FILE:-compose.yaml}" -for command in docker cosign; do +for command in docker skopeo cosign; do if ! command -v "$command" >/dev/null 2>&1; then echo "required command not found: $command" >&2 exit 1 @@ -17,7 +17,7 @@ if [[ ! "$verified_ref" =~ ^ghcr\.io/ploos-as/glowing-bear@sha256:[0-9a-f]{64}$ exit 1 fi -echo "Pulling verified immutable image: $verified_ref" +echo "Pulling verified immutable image with Docker: $verified_ref" docker pull "$verified_ref" export GLOWING_BEAR_IMAGE="$verified_ref" From 8d2a52eb1f40a4968e196c0db770266e35f45f7a Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:09:12 +0200 Subject: [PATCH 3/7] M0.13: remove Docker dependency from Quadlet rendering --- scripts/render-verified-quadlet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/render-verified-quadlet.sh b/scripts/render-verified-quadlet.sh index 5c18435..d0faff1 100644 --- a/scripts/render-verified-quadlet.sh +++ b/scripts/render-verified-quadlet.sh @@ -5,7 +5,7 @@ VERSION="${VERSION:?VERSION is required}" OUTPUT="${OUTPUT:-glowing-bear.verified.container}" TEMPLATE="${TEMPLATE:-quadlet/glowing-bear.container}" -for command in docker cosign; do +for command in skopeo cosign; do if ! command -v "$command" >/dev/null 2>&1; then echo "required command not found: $command" >&2 exit 1 From aaaf3148da365d8057e205d50f88a722a7c76eef Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:09:23 +0200 Subject: [PATCH 4/7] M0.13: qualify neutral verification in M0.11 gate --- .github/workflows/deployment-verification.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deployment-verification.yml b/.github/workflows/deployment-verification.yml index ad036cb..6cddbb5 100644 --- a/.github/workflows/deployment-verification.yml +++ b/.github/workflows/deployment-verification.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Validate M0.11 script syntax + - name: Validate verification script syntax run: bash -n scripts/verify-release.sh verify-deployment-policy: @@ -30,16 +30,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Install Skopeo + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends skopeo - name: Install Cosign uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - name: Select signed release id: release shell: bash @@ -51,8 +47,9 @@ jobs: version="0.2.3" fi echo "version=$version" >> "$GITHUB_OUTPUT" - - name: Verify before pull + - name: Verify before Docker pull env: VERSION: ${{ steps.release.outputs.version }} PULL: '1' + RUNTIME: docker run: bash scripts/verify-release.sh From 1fc46295fa21ee21200e5182de0b4be54d040710 Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:09:34 +0200 Subject: [PATCH 5/7] M0.13: remove Buildx from deployment enforcement --- .github/workflows/deployment-enforcement.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deployment-enforcement.yml b/.github/workflows/deployment-enforcement.yml index 014eeae..a395ad2 100644 --- a/.github/workflows/deployment-enforcement.yml +++ b/.github/workflows/deployment-enforcement.yml @@ -28,16 +28,12 @@ jobs: - name: Select release if: github.event_name == 'workflow_dispatch' run: echo "VERSION=${{ inputs.version }}" >> "$GITHUB_ENV" - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Install Skopeo + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends skopeo - name: Install Cosign uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - name: Validate scripts run: | bash -n scripts/deploy-verified-compose.sh From 4df5400f02deb6b03d8fd2e2530ea55108189c50 Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:09:47 +0200 Subject: [PATCH 6/7] M0.13: add Docker and Podman verification qualification --- .../runtime-neutral-verification.yml | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/runtime-neutral-verification.yml diff --git a/.github/workflows/runtime-neutral-verification.yml b/.github/workflows/runtime-neutral-verification.yml new file mode 100644 index 0000000..875ed83 --- /dev/null +++ b/.github/workflows/runtime-neutral-verification.yml @@ -0,0 +1,60 @@ +name: Runtime-neutral verification + +on: + pull_request: + workflow_dispatch: + inputs: + version: + description: Signed release version to qualify + required: true + default: '0.2.3' + +permissions: + contents: read + +env: + VERSION: '0.2.3' + +jobs: + qualify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Select release + if: github.event_name == 'workflow_dispatch' + run: echo "VERSION=${{ inputs.version }}" >> "$GITHUB_ENV" + - name: Install Skopeo and Podman + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends skopeo podman + - name: Install Cosign + uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 + - name: Validate script syntax + run: bash -n scripts/verify-release.sh + - name: Verify clean immutable output + shell: bash + run: | + set -euo pipefail + ref="$(VERSION="$VERSION" PULL=0 bash scripts/verify-release.sh)" + [[ "$ref" =~ ^ghcr\.io/ploos-as/glowing-bear@sha256:[0-9a-f]{64}$ ]] + - name: Qualify Docker pull path + shell: bash + run: | + set -euo pipefail + ref="$(VERSION="$VERSION" PULL=1 RUNTIME=docker bash scripts/verify-release.sh)" + docker image inspect "$ref" >/dev/null + - name: Qualify Podman pull path + shell: bash + run: | + set -euo pipefail + ref="$(VERSION="$VERSION" PULL=1 RUNTIME=podman bash scripts/verify-release.sh)" + podman image inspect "$ref" >/dev/null + - name: Reject wrong OIDC issuer + shell: bash + run: | + set -euo pipefail + if VERSION="$VERSION" OIDC_ISSUER=https://example.invalid PULL=0 bash scripts/verify-release.sh >/dev/null 2>&1; then + echo "verification unexpectedly accepted the wrong issuer" >&2 + exit 1 + fi From 309460e12d245b3c05830bbac3d3f600c5cd72e0 Mon Sep 17 00:00:00 2001 From: pos Date: Sat, 5 Sep 2026 14:10:04 +0200 Subject: [PATCH 7/7] M0.13: document runtime-neutral verification --- docs/M0_13_RUNTIME_NEUTRAL_VERIFICATION.md | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/M0_13_RUNTIME_NEUTRAL_VERIFICATION.md diff --git a/docs/M0_13_RUNTIME_NEUTRAL_VERIFICATION.md b/docs/M0_13_RUNTIME_NEUTRAL_VERIFICATION.md new file mode 100644 index 0000000..48f4352 --- /dev/null +++ b/docs/M0_13_RUNTIME_NEUTRAL_VERIFICATION.md @@ -0,0 +1,60 @@ +# M0.13 runtime-neutral verified deployment + +M0.13 removes Docker Buildx from the consumer trust path introduced in M0.11 and M0.12. + +## Trust path + +`scripts/verify-release.sh` now uses: + +1. Skopeo to resolve `ghcr.io/ploos-as/glowing-bear:` to the top-level immutable OCI digest. +2. Cosign to verify that exact digest against: + - certificate identity `https://github.com/Ploos-AS/glowing-bear/.github/workflows/sign-release.yml@refs/tags/v` + - OIDC issuer `https://token.actions.githubusercontent.com` +3. Docker or Podman only after signature verification when `PULL=1` is requested. + +The immutable image reference is the only stdout output. Diagnostics go to stderr so callers can safely use command substitution. + +## Runtime selection + +`RUNTIME` accepts: + +- `auto` (default): Docker when available, otherwise Podman. +- `docker`: require Docker for the post-verification pull. +- `podman`: require Podman for the post-verification pull. + +Verification without a pull does not require either container runtime. + +Examples: + +```bash +VERSION=0.2.3 bash scripts/verify-release.sh +VERSION=0.2.3 PULL=1 RUNTIME=docker bash scripts/verify-release.sh +VERSION=0.2.3 PULL=1 RUNTIME=podman bash scripts/verify-release.sh +``` + +## Deployment integration + +The verified Docker Compose path still uses Docker intentionally, but consumes the runtime-neutral verifier before pulling and starting the immutable image. + +The verified Quadlet renderer now requires only Skopeo and Cosign. It can therefore render a digest-pinned Podman Quadlet on a Podman-only host without Docker or Buildx installed. + +## CI qualification + +`Runtime-neutral verification` qualifies the signed `v0.2.3` release and requires: + +- clean immutable-ref stdout; +- successful Docker pull after verification; +- successful Podman pull after verification; +- rejection of a deliberately incorrect OIDC issuer. + +The existing deployment-verification and deployment-enforcement workflows also use Skopeo rather than Buildx for digest resolution. + +## Acceptance criteria + +M0.13 is complete when: + +- signature verification no longer depends on Docker or Buildx; +- Docker and Podman can each pull the exact verified digest; +- Quadlet rendering works without Docker; +- incorrect signer trust data fails closed; +- all existing container, relay, TLS/WSS, attestation, signature, deployment and release gates remain green.