Skip to content

Commit 82063bc

Browse files
committed
feat: generate SLSA provenance for product images
1 parent 7f60f9f commit 82063bc

1 file changed

Lines changed: 114 additions & 1 deletion

File tree

.github/workflows/reusable_build_image.yaml

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ jobs:
176176
persist-credentials: false
177177

178178
- name: Publish and Sign Image Index Manifest to oci.stackable.tech
179+
id: publish-oci
179180
if: inputs.publish
180181
uses: stackabletech/actions/publish-image-index-manifest@a8af17a19bdcc3b5da0065f76e73827ba0c072ce # v0.16.0
181182
with:
@@ -191,6 +192,7 @@ jobs:
191192
image-index-manifest-tag: ${{ matrix.versions }}-stackable${{ inputs.sdp-version }}
192193

193194
- name: Publish and Sign Image Index Manifest to quay.io
195+
id: publish-quay
194196
if: inputs.publish && inputs.publish-to-quay
195197
uses: stackabletech/actions/publish-image-index-manifest@a8af17a19bdcc3b5da0065f76e73827ba0c072ce # v0.16.0
196198
with:
@@ -205,9 +207,120 @@ jobs:
205207
image-repository: stackable/${{ inputs.registry-namespace }}/${{ inputs.image-name || inputs.product-name }}
206208
image-index-manifest-tag: ${{ matrix.versions }}-stackable${{ inputs.sdp-version }}
207209

210+
# Record the pushed index manifest digest(s) for this matrix version so the
211+
# provenance jobs can pick them up. Matrix jobs cannot expose per-version
212+
# outputs (they overwrite each other), so we pass the digests through
213+
# artifacts instead.
214+
- name: Record image index digests for provenance
215+
if: inputs.publish
216+
shell: bash
217+
env:
218+
VERSION: ${{ matrix.versions }}
219+
OCI_DIGEST: ${{ steps.publish-oci.outputs.image-index-manifest-digest }}
220+
QUAY_DIGEST: ${{ steps.publish-quay.outputs.image-index-manifest-digest }}
221+
run: |
222+
set -euo pipefail
223+
jq -n \
224+
--arg version "$VERSION" \
225+
--arg oci "$OCI_DIGEST" \
226+
--arg quay "$QUAY_DIGEST" \
227+
'{version: $version, oci_digest: $oci, quay_digest: $quay}' > digests.json
228+
229+
- name: Upload provenance digests
230+
if: inputs.publish
231+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
232+
with:
233+
name: provenance-digests-${{ matrix.versions }}
234+
path: digests.json
235+
retention-days: 1
236+
237+
# Collect the per-version index manifest digests uploaded by publish_manifests
238+
# and turn them into a matrix (one entry per version and registry) for the
239+
# provenance job. This is needed because a matrix job cannot expose per-leg
240+
# outputs directly.
241+
collect-provenance-matrix:
242+
name: Collect Provenance Matrix
243+
if: inputs.publish
244+
needs: [publish_manifests]
245+
permissions:
246+
contents: read
247+
runs-on: ubuntu-latest
248+
outputs:
249+
matrix: ${{ steps.build-matrix.outputs.matrix }}
250+
has-entries: ${{ steps.build-matrix.outputs.has_entries }}
251+
steps:
252+
- name: Download provenance digests
253+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
254+
with:
255+
pattern: provenance-digests-*
256+
path: artifacts
257+
258+
- name: Build provenance matrix
259+
id: build-matrix
260+
shell: bash
261+
env:
262+
REGISTRY_NAMESPACE: ${{ inputs.registry-namespace }}
263+
PRODUCT_NAME: ${{ inputs.product-name }}
264+
IMAGE_NAME: ${{ inputs.image-name }}
265+
run: |
266+
set -euo pipefail
267+
IMAGE="${IMAGE_NAME:-$PRODUCT_NAME}"
268+
269+
# Build a JSON matrix include array. Each version contributes one entry
270+
# for oci.stackable.tech and, if a quay digest was recorded, one for
271+
# quay.io. The slsa generator attaches provenance by digest, so no tag
272+
# is needed in the image reference.
273+
INCLUDE=$(jq -c -s \
274+
--arg ns "$REGISTRY_NAMESPACE" \
275+
--arg img "$IMAGE" '
276+
[ .[]
277+
| ( { registry: "oci.stackable.tech",
278+
image: ("oci.stackable.tech/" + $ns + "/" + $img),
279+
digest: .oci_digest,
280+
username: ("robot$" + $ns + "+github-action-build") } ),
281+
( if (.quay_digest // "") != ""
282+
then { registry: "quay.io",
283+
image: ("quay.io/stackable/" + $ns + "/" + $img),
284+
digest: .quay_digest,
285+
username: ("stackable+robot_" + $ns + "_github_action_build") }
286+
else empty end )
287+
]' artifacts/*/digests.json)
288+
289+
echo "matrix={\"include\":$INCLUDE}" | tee -a "$GITHUB_OUTPUT"
290+
if [ "$INCLUDE" = "[]" ]; then
291+
echo "has_entries=false" | tee -a "$GITHUB_OUTPUT"
292+
else
293+
echo "has_entries=true" | tee -a "$GITHUB_OUTPUT"
294+
fi
295+
296+
# Generate SLSA build provenance for every published multi-arch image index
297+
# (one matrix entry per version and registry) and attach it to the image. The
298+
# reusable workflow signs the provenance with keyless signing (GitHub Actions
299+
# as the OIDC identity) and pushes the attestation next to the image.
300+
provenance:
301+
name: Generate Provenance (${{ matrix.image }}@${{ matrix.digest }})
302+
if: inputs.publish && needs.collect-provenance-matrix.outputs.has-entries == 'true'
303+
needs: [collect-provenance-matrix]
304+
permissions:
305+
actions: read # detect the build workflow that generated the image
306+
id-token: write # mint the OIDC token for keyless signing
307+
packages: write # needed until https://github.com/slsa-framework/slsa-github-generator/issues/1257 is resolved
308+
strategy:
309+
fail-fast: false
310+
matrix: ${{ fromJson(needs.collect-provenance-matrix.outputs.matrix) }}
311+
# MUST be referenced by a @vX.Y.Z tag (not a SHA), otherwise the reusable
312+
# workflow cannot verify its own provenance.
313+
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
314+
with:
315+
image: ${{ matrix.image }}
316+
digest: ${{ matrix.digest }}
317+
registry-username: ${{ matrix.username }}
318+
secrets:
319+
registry-password: ${{ matrix.registry == 'quay.io' && secrets.quay-robot-secret || secrets.harbor-robot-secret }}
320+
208321
notify:
209322
name: Failure Notification
210-
needs: [generate_version_dimension, build, publish_manifests]
323+
needs: [generate_version_dimension, build, publish_manifests, collect-provenance-matrix, provenance]
211324
runs-on: ubuntu-latest
212325
# TODO (@NickLarsenNZ): Allow a condition from input so that we can always
213326
# be notified of new builds for precompiled product images.

0 commit comments

Comments
 (0)