From 1a3758f80c72d853576cf427371efd2f08e4fda4 Mon Sep 17 00:00:00 2001 From: gonzaloaune <219565+gonzaloaune@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:24:47 +0000 Subject: [PATCH] Attach source/commit provenance to published container images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I added source/revision labels and build-provenance attestation to both Docker Hub publishing workflows. Nothing has been run, so none of it is tested. **Registries found:** Docker Hub only, as `${{ secrets.DOCKER_HUB_USER }}/graphmindset`. There is no ghcr.io push. `build-check.yml` doesn't publish, so I left it alone. **Workflows changed:** - `.github/workflows/docker-build.yml` (on release; tags `RELEASE_TAG` and `latest`). - `.github/workflows/docker-build-main.yml` (on push to main; tag `main`). **Changes in each:** - **Labels:** both workflows use plain `docker build` and `docker push`, not `build-push-action`. I added `--label org.opencontainers.image.source=/` and `--label org.opencontainers.image.revision=` to the `docker build` command. I did not add `docker/metadata-action`, because there is no build-push-action step to pass its outputs to. So there are no manifest annotations, only labels. - **Permissions:** neither job had a permissions block, so I added `contents: read`, `id-token: write` and `attestations: write`. `packages: write` isn't needed because nothing goes to ghcr. - **Digest:** a new step resolves the pushed digest with `docker buildx imagetools inspect : --format '{{.Manifest.Digest}}'`. In the release workflow it inspects the `RELEASE_TAG` tag, which points at the same image as `latest`. - **Attestation:** `actions/attest-build-provenance@v2` runs with `subject-name: index.docker.io/${{ secrets.DOCKER_HUB_USER }}/graphmindset`, the digest from the step above, and `push-to-registry: true`. It has the `gh attestation verify oci://… --owner stakwork` comment above it. Existing tags, triggers, login and push steps are unchanged. Neither workflow sets any provenance or sbom option, so there is nothing to carry over there. --- .github/workflows/docker-build-main.yml | 17 ++++++++++++++++- .github/workflows/docker-build.yml | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build-main.yml b/.github/workflows/docker-build-main.yml index 2cfd072d..9d610e6c 100644 --- a/.github/workflows/docker-build-main.yml +++ b/.github/workflows/docker-build-main.yml @@ -10,6 +10,10 @@ on: jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write name: Build and push graphmindset image env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true @@ -23,5 +27,16 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push run: | - docker build --no-cache -t "${{ secrets.DOCKER_HUB_USER }}/graphmindset:main" . + docker build --no-cache --label org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} --label org.opencontainers.image.revision=${{ github.sha }} -t "${{ secrets.DOCKER_HUB_USER }}/graphmindset:main" . docker push "${{ secrets.DOCKER_HUB_USER }}/graphmindset:main" + - name: Get image digest + id: digest + run: | + echo "digest=$(docker buildx imagetools inspect "${{ secrets.DOCKER_HUB_USER }}/graphmindset:main" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT" + # Verify with: gh attestation verify oci://index.docker.io//graphmindset@sha256: --owner stakwork + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-name: index.docker.io/${{ secrets.DOCKER_HUB_USER }}/graphmindset + subject-digest: ${{ steps.digest.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 301503c0..403c23ce 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -9,6 +9,10 @@ on: jobs: push: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write name: Build and push graphmindset image steps: - name: Check out from Git @@ -22,6 +26,17 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push run: | - docker build --no-cache -t "${{ secrets.DOCKER_HUB_USER }}/graphmindset:${{ env.RELEASE_TAG }}" -t "${{ secrets.DOCKER_HUB_USER }}/graphmindset:latest" . + docker build --no-cache --label org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} --label org.opencontainers.image.revision=${{ github.sha }} -t "${{ secrets.DOCKER_HUB_USER }}/graphmindset:${{ env.RELEASE_TAG }}" -t "${{ secrets.DOCKER_HUB_USER }}/graphmindset:latest" . docker push "${{ secrets.DOCKER_HUB_USER }}/graphmindset:${{ env.RELEASE_TAG }}" docker push "${{ secrets.DOCKER_HUB_USER }}/graphmindset:latest" + - name: Get image digest + id: digest + run: | + echo "digest=$(docker buildx imagetools inspect "${{ secrets.DOCKER_HUB_USER }}/graphmindset:${{ env.RELEASE_TAG }}" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT" + # Verify with: gh attestation verify oci://index.docker.io//graphmindset@sha256: --owner stakwork + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-name: index.docker.io/${{ secrets.DOCKER_HUB_USER }}/graphmindset + subject-digest: ${{ steps.digest.outputs.digest }} + push-to-registry: true