Saltar al contenido
mypipelines
Shared (cross-cutting)·Reusable workflow·on: workflow_call

Shared Sign Images

Shared - Sign Container Images

.github/workflows/shared-sign-images.yml

.github/workflows/shared-sign-images.yml
name: Shared - Sign Container Images
# Signs what shared-build-publish-image.yml pushed, with cosign keyless.
#
# The publish workflow already attaches an SBOM and provenance, but nothing ties
# a digest to the pipeline that built it: anyone who can push to the package can
# push an image with the same tags. A keyless signature is a certificate from
# Sigstore's Fulcio, issued against this workflow run's GitHub OIDC token and
# logged in Rekor, so a consumer can verify that a digest came from a given
# repository and workflow without anyone holding a key.
#
# Separate from the publish workflow on purpose. Signing needs `id-token: write`,
# and a reusable workflow cannot be granted more than its caller has: putting it
# in the publish workflow would make every caller that does not grant it fail
# before the first step. Here only callers who opt in add the job and the
# permission.
#
# Run it after the publish job in the same workflow run. It reads the
# `image-digest-*` artifacts that job uploads, so it signs exactly the digests
# that were pushed — never a tag, which could have moved in between.
on:
workflow_call:
inputs:
registry:
description: 'Container registry the images were pushed to'
required: false
type: string
default: 'ghcr.io'
runner:
description: 'Runner type'
required: false
type: string
default: 'ubuntu-latest'
permissions:
contents: read
packages: write
id-token: write
jobs:
sign:
name: Sign images
runs-on: ${{ inputs.runner }}
timeout-minutes: 15
steps:
- name: Download pushed digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: image-digest-*
path: digests
merge-multiple: true
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: 'v3.1.3'
- name: Login to the registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ inputs.registry }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# By digest only. A ref without one is refused rather than signed: a tag
# can move between the push and this step, and signing whatever it points
# at now would vouch for an image this run never built.
- name: Sign
run: |
set -euo pipefail
shopt -s nullglob
files=(digests/image-digest-*.txt)
if [ ${#files[@]} -eq 0 ]; then
echo "::error::No image-digest-* artifacts found. Run this after shared-build-publish-image.yml with push: true, in the same workflow run."
exit 1
fi
# Validate every ref before signing any, so a bad one fails the run
# without leaving half the set signed.
refs=()
for f in "${files[@]}"; do
ref=$(tr -d '[:space:]' < "${f}")
case "${ref}" in
*@sha256:*) refs+=("${ref}") ;;
*) echo "::error::${f} holds '${ref}', not a digest reference; refusing to sign anything."; exit 1 ;;
esac
done
{
echo "### Signed images"
echo ""
echo "| Image |"
echo "|-------|"
} >> "$GITHUB_STEP_SUMMARY"
for ref in "${refs[@]}"; do
cosign sign --yes "${ref}"
echo "| \`${ref}\` |" >> "$GITHUB_STEP_SUMMARY"
done