ci: sign every published runner image by digest (zegit#262) #13
@@ -24,6 +24,11 @@ on:
|
|||||||
description: 'Image tag, e.g. 12.11.1-ci6 (upstream runner version + ci revision)'
|
description: 'Image tag, e.g. 12.11.1-ci6 (upstream runner version + ci revision)'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
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:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
@@ -35,6 +40,7 @@ env:
|
|||||||
BUILDKIT_VERSION: v0.32.2
|
BUILDKIT_VERSION: v0.32.2
|
||||||
REGISTRY: gitea.langenoja.se
|
REGISTRY: gitea.langenoja.se
|
||||||
IMAGE_PATH: zegit-zoo/zegit-forgejo-runner
|
IMAGE_PATH: zegit-zoo/zegit-forgejo-runner
|
||||||
|
COSIGN_VERSION: v3.1.3
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -78,6 +84,7 @@ jobs:
|
|||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
echo "push=true" >> "$GITHUB_OUTPUT"
|
echo "push=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
|
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "sign_only=${{ inputs.sign_only }}" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "push=false" >> "$GITHUB_OUTPUT"
|
echo "push=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "tag=ci-validation" >> "$GITHUB_OUTPUT"
|
echo "tag=ci-validation" >> "$GITHUB_OUTPUT"
|
||||||
@@ -103,6 +110,7 @@ jobs:
|
|||||||
> "$HOME/.docker/config.json"
|
> "$HOME/.docker/config.json"
|
||||||
|
|
||||||
- name: Build multi-arch
|
- name: Build multi-arch
|
||||||
|
if: ${{ steps.meta.outputs.sign_only != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
REF="${REGISTRY}/${IMAGE_PATH}:${{ steps.meta.outputs.tag }}"
|
REF="${REGISTRY}/${IMAGE_PATH}:${{ steps.meta.outputs.tag }}"
|
||||||
@@ -165,6 +173,54 @@ jobs:
|
|||||||
--opt platform=linux/amd64,linux/arm64
|
--opt platform=linux/amd64,linux/arm64
|
||||||
echo "✓ toolchain intact on linux/amd64 and linux/arm64"
|
echo "✓ toolchain intact on linux/amd64 and linux/arm64"
|
||||||
|
|
||||||
|
# zegit#143/#262: a -ci<N> 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
|
- name: Summary
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ steps.meta.outputs.push }}" = "true" ]; then
|
if [ "${{ steps.meta.outputs.push }}" = "true" ]; then
|
||||||
|
|||||||
Reference in new issue
Block a user