diff --git a/.github/workflows/build-image-gitea.yml b/.github/workflows/build-image-gitea.yml index c7ff791..3f3a069 100644 --- a/.github/workflows/build-image-gitea.yml +++ b/.github/workflows/build-image-gitea.yml @@ -24,6 +24,11 @@ on: description: 'Image tag, e.g. 12.11.1-ci6 (upstream runner version + ci revision)' required: true type: string + sign_only: + description: 'Sign the already-published tag instead of building it (zegit#262: signs an image published before signing existed)' + required: false + default: false + type: boolean push: branches: [master] paths: @@ -35,6 +40,7 @@ env: BUILDKIT_VERSION: v0.32.2 REGISTRY: gitea.langenoja.se IMAGE_PATH: zegit-zoo/zegit-forgejo-runner + COSIGN_VERSION: v3.1.3 jobs: build: @@ -78,6 +84,7 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "push=true" >> "$GITHUB_OUTPUT" echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" + echo "sign_only=${{ inputs.sign_only }}" >> "$GITHUB_OUTPUT" else echo "push=false" >> "$GITHUB_OUTPUT" echo "tag=ci-validation" >> "$GITHUB_OUTPUT" @@ -103,6 +110,7 @@ jobs: > "$HOME/.docker/config.json" - name: Build multi-arch + if: ${{ steps.meta.outputs.sign_only != 'true' }} run: | set -euo pipefail REF="${REGISTRY}/${IMAGE_PATH}:${{ steps.meta.outputs.tag }}" @@ -165,6 +173,54 @@ jobs: --opt platform=linux/amd64,linux/arm64 echo "✓ toolchain intact on linux/amd64 and linux/arm64" + # zegit#143/#262: a -ci tag is only a name, and ZEGIT_CI_PACKAGE_TOKEN + # (readable by every zegit-zoo branch run) can push one. Consumers pin + # this image only when `cosign verify` accepts its digest with the + # runner-image public key, so every published digest is signed here. + # The private key is a REPOSITORY secret of the runner repos only, never + # an org secret. Classic .sig tag, no transparency log: the registry is + # private and verifiers use --insecure-ignore-tlog with a pinned key. + - name: Sign the published image by digest + if: ${{ steps.meta.outputs.push == 'true' }} + env: + COSIGN_KEY_PEM: ${{ secrets.RUNNER_IMAGE_COSIGN_KEY }} + COSIGN_PASSWORD: ${{ secrets.RUNNER_IMAGE_COSIGN_PASSWORD }} + TOKEN: ${{ secrets.ZEGIT_CI_PACKAGE_TOKEN }} + run: | + set -euo pipefail + if [ -z "${COSIGN_KEY_PEM:-}" ] || [ -z "${COSIGN_PASSWORD:-}" ]; then + echo "::error::RUNNER_IMAGE_COSIGN_KEY / RUNNER_IMAGE_COSIGN_PASSWORD are not visible to this run (repository secrets, zegit#262). An unsigned runner image is never pinned." + exit 1 + fi + case "$(uname -m)" in + x86_64) arch=amd64; sum=4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71;; + aarch64) arch=arm64; sum=c5d324e091826b0d7a78eb16fef316450b4eb9aaec045611c08ba06f5e73220a;; + *) echo "::error::unsupported arch $(uname -m)"; exit 1;; + esac + bin="$GITHUB_WORKSPACE/.bin/cosign" + curl -fsSL -o "$bin" "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-${arch}" + echo "${sum} ${bin}" | sha256sum -c - + chmod +x "$bin" + tag="${{ steps.meta.outputs.tag }}" + tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT + # Credentials go through 0600 curl configs, never argv (zegit#139). + ( umask 077; printf 'user = "zegit-zoo:%s"\n' "$TOKEN" > "$tmp/basic.cfg" ) + tok=$(curl -fsS -K "$tmp/basic.cfg" "https://${REGISTRY}/v2/token?scope=repository:${IMAGE_PATH}:pull&service=container_registry" | + python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])') + ( umask 077; printf 'header = "Authorization: Bearer %s"\n' "$tok" > "$tmp/bearer.cfg" ) + digest=$(curl -fsS -I -K "$tmp/bearer.cfg" \ + -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json' \ + "https://${REGISTRY}/v2/${IMAGE_PATH}/manifests/${tag}" | tr -d '\r' | + awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}') + [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "::error::no digest for ${IMAGE_PATH}:${tag}"; exit 1; } + ref="${REGISTRY}/${IMAGE_PATH}@${digest}" + printf '%s\n' "$COSIGN_KEY_PEM" > "$tmp/cosign.key"; chmod 600 "$tmp/cosign.key" + "$bin" sign --yes --key "$tmp/cosign.key" --tlog-upload=false \ + --new-bundle-format=false --use-signing-config=false "$ref" + "$bin" public-key --key "$tmp/cosign.key" > "$tmp/cosign.pub" + "$bin" verify --key "$tmp/cosign.pub" --insecure-ignore-tlog "${REGISTRY}/${IMAGE_PATH}:${tag}@${digest}" >/dev/null + echo "✓ signed and verified ${REGISTRY}/${IMAGE_PATH}:${tag}@${digest}" + - name: Summary run: | if [ "${{ steps.meta.outputs.push }}" = "true" ]; then