Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions .github/workflows/deployment-enforcement.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 7 additions & 10 deletions .github/workflows/deployment-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,20 @@ 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:
runs-on: ubuntu-latest
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
Expand All @@ -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
60 changes: 60 additions & 0 deletions .github/workflows/runtime-neutral-verification.yml
Original file line number Diff line number Diff line change
@@ -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
60 changes: 60 additions & 0 deletions docs/M0_13_RUNTIME_NEUTRAL_VERIFICATION.md
Original file line number Diff line number Diff line change
@@ -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:<version>` 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<VERSION>`
- 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.
4 changes: 2 additions & 2 deletions scripts/deploy-verified-compose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion scripts/render-verified-quadlet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 30 additions & 8 deletions scripts/verify-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"
Loading