From dc5c7fd3ba1acf19bbbad5cec2325eca2bbb5499 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Tue, 4 Aug 2026 09:01:19 +0100 Subject: [PATCH 1/2] Sign published container images --- .github/workflows/publish.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e2b1084..6d57999 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,6 +18,7 @@ jobs: permissions: packages: write contents: read + id-token: write steps: - name: Check out the repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 @@ -45,7 +46,10 @@ jobs: with: images: ghcr.io/${{ github.repository }} + - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + - name: Build and push Docker image + id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: . @@ -53,3 +57,26 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + provenance: mode=max + sbom: true + + - name: Sign image by digest + env: + DIGEST: ${{ steps.build.outputs.digest }} + IMAGE: ghcr.io/${{ github.repository }} + run: | + set -euo pipefail + [[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] + cosign sign --yes "${IMAGE}@${DIGEST}" + + - name: Verify remote provenance and SBOM attestations + env: + DIGEST: ${{ steps.build.outputs.digest }} + IMAGE: ghcr.io/${{ github.repository }} + run: | + set -euo pipefail + reference="${IMAGE}@${DIGEST}" + docker buildx imagetools inspect "$reference" --format '{{ json .Provenance }}' \ + | jq -e 'type == "object" and length > 0' >/dev/null + docker buildx imagetools inspect "$reference" --format '{{ json .SBOM }}' \ + | jq -e 'type == "object" and length > 0' >/dev/null From 7edec36d0dbb3e5154dc163cc57caff2405279bc Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Mon, 10 Aug 2026 09:43:18 +0100 Subject: [PATCH 2/2] Attest per-platform SPDX SBOMs with cosign Extract each platform's SPDX document from the BuildKit SBOM attestation and sign it as a cosign spdxjson attestation against the manifest-list digest, so downstream consumers (e.g. Kyverno image-verification policies) can verify the predicate signature rather than relying on the unsigned BuildKit attachment. --- .github/workflows/publish.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6d57999..1c4eb1e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -69,14 +69,30 @@ jobs: [[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] cosign sign --yes "${IMAGE}@${DIGEST}" - - name: Verify remote provenance and SBOM attestations + - name: Verify BuildKit attestations and extract SPDX predicates env: DIGEST: ${{ steps.build.outputs.digest }} IMAGE: ghcr.io/${{ github.repository }} run: | set -euo pipefail reference="${IMAGE}@${DIGEST}" - docker buildx imagetools inspect "$reference" --format '{{ json .Provenance }}' \ - | jq -e 'type == "object" and length > 0' >/dev/null - docker buildx imagetools inspect "$reference" --format '{{ json .SBOM }}' \ - | jq -e 'type == "object" and length > 0' >/dev/null + docker buildx imagetools inspect "$reference" --format '{{ json .Provenance }}' > provenance.json + docker buildx imagetools inspect "$reference" --format '{{ json .SBOM }}' > sbom.json + + for platform in linux/amd64 linux/arm64; do + jq -e --arg p "$platform" '.[$p].SLSA | type == "object" and length > 0' \ + provenance.json >/dev/null + jq -e --arg p "$platform" '.[$p].SPDX' sbom.json > "sbom-${platform//\//-}.spdx.json" + done + + - name: Attest platform SBOMs by digest + env: + DIGEST: ${{ steps.build.outputs.digest }} + IMAGE: ghcr.io/${{ github.repository }} + run: | + set -euo pipefail + [[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] + reference="${IMAGE}@${DIGEST}" + for predicate in sbom-linux-amd64.spdx.json sbom-linux-arm64.spdx.json; do + cosign attest --yes --type spdxjson --predicate "$predicate" "$reference" + done