From 554490885de881616fff0f2eb511742da13debc5 Mon Sep 17 00:00:00 2001 From: xnoto Date: Sat, 12 Sep 2026 12:19:35 -0600 Subject: [PATCH] ci: attest published image SBOMs --- .github/workflows/buildah.yml | 64 ++++++++++++++++++++++++++++++++++- AGENTS.md | 6 +++- README.md | 9 ++++- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/.github/workflows/buildah.yml b/.github/workflows/buildah.yml index f787d6e..cc59c91 100644 --- a/.github/workflows/buildah.yml +++ b/.github/workflows/buildah.yml @@ -158,4 +158,66 @@ jobs: tags: latest ${{ github.sha }} registry: ghcr.io/makeitworkcloud username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + password: ${{ secrets.GITHUB_TOKEN }} + + attest: + name: attest ${{ matrix.image }} + permissions: + contents: read + packages: write + attestations: write + artifact-metadata: write + id-token: write + needs: [build, detect] + if: >- + needs.build.result == 'success' && + ((github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'workflow_dispatch' && inputs.mode == 'build & push')) + runs-on: ubuntu-latest + strategy: + matrix: + image: ${{ fromJson(needs.detect.outputs.images) }} + + steps: + - name: Install skopeo + run: | + sudo apt-get update + sudo apt-get install -y skopeo + + - name: Resolve published image digest + id: image + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + image_name="ghcr.io/makeitworkcloud/${{ matrix.image }}" + echo "$GITHUB_TOKEN" | skopeo login ghcr.io --username "${{ github.actor }}" --password-stdin + digest="$(skopeo inspect --format '{{.Digest}}' "docker://${image_name}:${GITHUB_SHA}")" + printf 'name=%s\n' "$image_name" >> "$GITHUB_OUTPUT" + printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT" + + - name: Generate SBOM + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 + with: + image: ${{ steps.image.outputs.name }}@${{ steps.image.outputs.digest }} + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} + output-file: ${{ matrix.image }}.spdx.json + format: spdx-json + syft-version: v1.51.1 + upload-artifact: false + + - name: Attest build provenance + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 + with: + subject-name: ${{ steps.image.outputs.name }} + subject-digest: ${{ steps.image.outputs.digest }} + push-to-registry: true + + - name: Attest SBOM + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 + with: + subject-name: ${{ steps.image.outputs.name }} + subject-digest: ${{ steps.image.outputs.digest }} + sbom-path: ${{ matrix.image }}.spdx.json + push-to-registry: true diff --git a/AGENTS.md b/AGENTS.md index 4f41910..9b03a16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ production publication. ## Build Workflow (`buildah.yml`) -Single workflow, three jobs, all on `ubuntu-latest`. +Single workflow, four jobs, all on `ubuntu-latest`. 1. **checks** — runs pre-commit, including full-tree Gitleaks scanning, hadolint, and actionlint, for every `main` push, pull request, and manual @@ -39,6 +39,10 @@ Single workflow, three jobs, all on `ubuntu-latest`. - on `push` to `main`, or `workflow_dispatch` with `mode=build & push`, push to GHCR with tags `latest` and `${{ github.sha }}` PRs and `workflow_dispatch` with `mode=build` build but do not push. +4. **attest** — after an image is pushed, resolve its immutable GHCR digest, + generate an SPDX SBOM with Syft, and publish both SBOM and build-provenance + attestations. This job runs only for `main` pushes and explicit publishing + dispatches; it must not run for PR builds. ## Makefile diff --git a/README.md b/README.md index 3164f1e..1b4ebe8 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,20 @@ Container image monorepo. Each subdirectory containing a `Containerfile` is buil ## How It Works ``` -push to main ─▶ detect changed images ─▶ pre-commit + hadolint ─▶ buildah build ─▶ push to GHCR +push to main ─▶ detect changed images ─▶ pre-commit + hadolint ─▶ buildah build ─▶ push to GHCR ─▶ attest digest ``` `workflow_dispatch` accepts an optional `image` input to rebuild a single image; with no input it builds all images. Manual dispatch defaults to **build & push**; select **build** for a non-publishing validation run. The detect step uses the `Makefile` (`make changed-images` / `make list-images-json`) to enumerate directories that contain a `Containerfile`. +## Supply-chain attestations + +After a publishing build, CI resolves the immutable digest behind the SHA tag, +generates an SPDX JSON SBOM from that digest, and attaches both the SBOM and +build provenance to the GHCR image. PR and non-publishing manual builds do not +publish images or attestations. + ## Adding an Image 1. Create `/Containerfile`