From fc606ec8fc4f85293c1484899b189728567f552c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin <3811295@gmail.com> Date: Tue, 21 Jul 2026 23:07:57 +0300 Subject: [PATCH] ci: sign the released images with cosign (#963) Published images carry no signature, so there is no way for a user to verify that the image they pull was built by this workflow from this repository. Sign them keyless, using the Github OIDC token, so verification needs no key material on either side. The manifest is signed by digest rather than by tag: every tag produced for a release points at the same digest, and the same digest is valid in both registries the build pushes to, so signing each tag would only repeat the same signature. Verify with: cosign verify ghcr.io/donkie/spoolman: \ --certificate-identity-regexp '^https://github\.com/Donkie/Spoolman/\.github/workflows/ci\.yml@refs/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 014dcccb..3d10fc7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -393,6 +393,8 @@ jobs: runs-on: ubuntu-latest permissions: packages: write + # Lets cosign fetch an OIDC token from Github and sign without a private key. + id-token: write steps: - name: Checkout uses: actions/checkout@v6 @@ -452,7 +454,15 @@ jobs: echo "GIT_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV" echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$GITHUB_ENV" + - name: Install cosign + uses: sigstore/cosign-installer@v4.1.2 + with: + # Installer v4+ verifies cosign v3 downloads via the Sigstore bundle + # format; pin an explicit, current cosign release for reproducibility. + cosign-release: v3.1.1 + - name: Build and push Docker images + id: build uses: docker/build-push-action@v6.7.0 with: context: . @@ -469,6 +479,24 @@ jobs: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64 type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64 type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7 + + # Sign the manifest by digest rather than by tag: all the tags above point + # at the same digest, and the same digest is valid in both registries, so + # signing every tag would just repeat the same signature. + - name: Sign the published images + env: + DIGEST: ${{ steps.build.outputs.digest }} + TAGS: | + ${{ steps.meta-ghcr.outputs.tags }} + ${{ steps.meta-dh.outputs.tags }} + run: | + echo "${TAGS}" | while read -r tag; do + [ -n "${tag}" ] || continue + echo "${tag%:*}" + done | sort --unique | while read -r repository; do + echo "Signing ${repository}@${DIGEST}" + cosign sign --yes "${repository}@${DIGEST}" + done # # Create Github Release if CI was triggered by a tag #