From 3f67c49e1cd906698688e539f4f99dcea972850f Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 20:42:10 -0800 Subject: [PATCH 1/8] feat(ci): add publish-src workflow for test image Add a GitHub Actions workflow that publishes the repository contents as a blob archive to ghcr.io/meigma/blob-cli/src:latest on merge to master. Features: - Sigstore keyless signing - SLSA provenance generation via slsa-github-generator - Self-verification against policy file The workflow enables consumers to test blob CLI operations against a known-good signed and attested archive. Co-Authored-By: Claude Opus 4.5 --- .github/policies/src-image.yaml | 16 +++++ .github/workflows/publish-src.yml | 104 ++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 .github/policies/src-image.yaml create mode 100644 .github/workflows/publish-src.yml diff --git a/.github/policies/src-image.yaml b/.github/policies/src-image.yaml new file mode 100644 index 0000000..7bad5c2 --- /dev/null +++ b/.github/policies/src-image.yaml @@ -0,0 +1,16 @@ +# Policy for verifying the blob-cli source archive +# +# This policy requires: +# 1. A Sigstore keyless signature from the publish-src workflow +# 2. SLSA provenance generated by the SLSA GitHub generator + +signature: + keyless: + issuer: https://token.actions.githubusercontent.com + identity: https://github.com/meigma/blob-cli/.github/workflows/* + +provenance: + slsa: + builder: https://github.com/slsa-framework/slsa-github-generator/.github/workflows/* + repository: meigma/blob-cli + branch: master diff --git a/.github/workflows/publish-src.yml b/.github/workflows/publish-src.yml new file mode 100644 index 0000000..e776274 --- /dev/null +++ b/.github/workflows/publish-src.yml @@ -0,0 +1,104 @@ +name: Publish Source Archive + +on: + pull_request: + branches: [master] + # TODO: Revert to push-only after testing + # push: + # branches: [master] + +permissions: + contents: read + packages: write + id-token: write # Required for Sigstore keyless signing and SLSA + +env: + IMAGE_NAME: ghcr.io/meigma/blob-cli/src + IMAGE_TAG: latest + +jobs: + build-and-push: + name: Build and Push + runs-on: ubuntu-latest + outputs: + digest: ${{ steps.push.outputs.digest }} + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version: "1.25" + cache: true + + - name: Build blob CLI + run: go build -o blob . + + - name: Log in to GHCR + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push archive with signing + id: push + run: | + ./blob push --sign "${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}" . + + # Get the digest using inspect + DIGEST=$(./blob inspect --output json "${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}" | jq -r '.digest') + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" + echo "Pushed with digest: ${DIGEST}" + + provenance: + name: Generate SLSA Provenance + needs: [build-and-push] + permissions: + actions: read + id-token: write + packages: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 + with: + image: ghcr.io/meigma/blob-cli/src + digest: ${{ needs.build-and-push.outputs.digest }} + registry-username: ${{ github.actor }} + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} + + verify: + name: Verify Archive + needs: [build-and-push, provenance] + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version: "1.25" + cache: true + + - name: Build blob CLI + run: go build -o blob . + + - name: Log in to GHCR + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify with policy + run: | + ./blob verify \ + --policy .github/policies/src-image.yaml \ + "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" From 1a0ff00403ae7a07903de2d43e9af7c2cc921beb Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 20:47:35 -0800 Subject: [PATCH 2/8] fix(ci): update policy identity to include ref pattern The Sigstore identity includes the full workflow path with ref suffix, e.g., .../publish-src.yml@refs/heads/master. Update pattern to match. Co-Authored-By: Claude Opus 4.5 --- .github/policies/src-image.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/policies/src-image.yaml b/.github/policies/src-image.yaml index 7bad5c2..f720701 100644 --- a/.github/policies/src-image.yaml +++ b/.github/policies/src-image.yaml @@ -7,7 +7,8 @@ signature: keyless: issuer: https://token.actions.githubusercontent.com - identity: https://github.com/meigma/blob-cli/.github/workflows/* + # Identity includes workflow path and ref, e.g., .../publish-src.yml@refs/heads/master + identity: https://github.com/meigma/blob-cli/.github/workflows/publish-src.yml@* provenance: slsa: From 79991ba81ae947e4f2e10fee8bd541c3979eb882 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 20:53:09 -0800 Subject: [PATCH 3/8] fix(ci): use SLSA provenance verification only The CLI's YAML policy format for signature.keyless.identity uses exact string matching via sigstore.WithIdentity, which doesn't support the pattern matching needed for GitHub Actions workflow identity verification. Switch to SLSA provenance verification only, which properly handles GitHub Actions workflow matching. The SLSA attestation is itself signed, maintaining supply chain security. Co-Authored-By: Claude Opus 4.5 --- .github/policies/src-image.yaml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/policies/src-image.yaml b/.github/policies/src-image.yaml index f720701..4c55c71 100644 --- a/.github/policies/src-image.yaml +++ b/.github/policies/src-image.yaml @@ -1,17 +1,13 @@ # Policy for verifying the blob-cli source archive # -# This policy requires: -# 1. A Sigstore keyless signature from the publish-src workflow -# 2. SLSA provenance generated by the SLSA GitHub generator - -signature: - keyless: - issuer: https://token.actions.githubusercontent.com - # Identity includes workflow path and ref, e.g., .../publish-src.yml@refs/heads/master - identity: https://github.com/meigma/blob-cli/.github/workflows/publish-src.yml@* +# This policy verifies SLSA provenance generated by the SLSA GitHub generator. +# The SLSA attestation is itself signed, providing supply chain security. +# +# Note: We rely on SLSA provenance rather than the blob signature because +# the CLI's YAML policy format uses exact identity matching, which doesn't +# support the ref suffix pattern matching needed for GitHub Actions workflows. provenance: slsa: - builder: https://github.com/slsa-framework/slsa-github-generator/.github/workflows/* repository: meigma/blob-cli branch: master From 5af8985d638fa6f3c7ce3d904da3485150fd911f Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 20:57:56 -0800 Subject: [PATCH 4/8] debug: add attestation inspection step --- .github/workflows/publish-src.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/publish-src.yml b/.github/workflows/publish-src.yml index e776274..47b8a08 100644 --- a/.github/workflows/publish-src.yml +++ b/.github/workflows/publish-src.yml @@ -97,6 +97,24 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Debug - Inspect archive + run: | + echo "=== Inspecting archive ===" + ./blob inspect --output json "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" | jq . + + echo "" + echo "=== Checking for referrers with oras ===" + go install oras.land/oras/cmd/oras@latest + oras discover "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" --output tree || echo "oras discover failed" + + echo "" + echo "=== Checking for attestation tag ===" + # SLSA generator may use tag-based fallback + DIGEST="${{ needs.build-and-push.outputs.digest }}" + TAG="${DIGEST//:/\-}" + echo "Looking for tag: $TAG.att" + oras manifest fetch "${{ env.IMAGE_NAME }}:$TAG.att" 2>&1 || echo "No attestation tag found" + - name: Verify with policy run: | ./blob verify \ From 266143046c7ef1fdc5205826b44c93bc22efe17b Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 21:01:30 -0800 Subject: [PATCH 5/8] fix(ci): verify attestation via oras instead of blob verify The SLSA GitHub generator stores attestations using tag-based approach (sha256-.att tags) rather than the OCI Referrers API. The blob CLI currently only discovers attestations via referrers. Switch to manual attestation verification using oras until tag-based attestation discovery is added to the blob library. Co-Authored-By: Claude Opus 4.5 --- .github/policies/src-image.yaml | 11 ++++---- .github/workflows/publish-src.yml | 44 ++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/.github/policies/src-image.yaml b/.github/policies/src-image.yaml index 4c55c71..5c64830 100644 --- a/.github/policies/src-image.yaml +++ b/.github/policies/src-image.yaml @@ -1,11 +1,12 @@ # Policy for verifying the blob-cli source archive # -# This policy verifies SLSA provenance generated by the SLSA GitHub generator. -# The SLSA attestation is itself signed, providing supply chain security. +# This policy is intended for use with `blob verify` once tag-based attestation +# discovery is supported. Currently, the SLSA generator uses tag-based storage +# (sha256-.att) which the blob CLI doesn't yet discover. # -# Note: We rely on SLSA provenance rather than the blob signature because -# the CLI's YAML policy format uses exact identity matching, which doesn't -# support the ref suffix pattern matching needed for GitHub Actions workflows. +# The workflow validates attestations manually using oras until this is implemented. +# +# TODO: Enable policy verification once blob library supports tag-based attestation discovery provenance: slsa: diff --git a/.github/workflows/publish-src.yml b/.github/workflows/publish-src.yml index 47b8a08..2b54a45 100644 --- a/.github/workflows/publish-src.yml +++ b/.github/workflows/publish-src.yml @@ -97,26 +97,38 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Debug - Inspect archive + - name: Verify archive and attestations run: | echo "=== Inspecting archive ===" - ./blob inspect --output json "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" | jq . + ./blob inspect "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" echo "" - echo "=== Checking for referrers with oras ===" - go install oras.land/oras/cmd/oras@latest - oras discover "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" --output tree || echo "oras discover failed" - - echo "" - echo "=== Checking for attestation tag ===" - # SLSA generator may use tag-based fallback + echo "=== Verifying SLSA attestation exists ===" + # The SLSA generator uses tag-based storage for attestations DIGEST="${{ needs.build-and-push.outputs.digest }}" TAG="${DIGEST//:/\-}" - echo "Looking for tag: $TAG.att" - oras manifest fetch "${{ env.IMAGE_NAME }}:$TAG.att" 2>&1 || echo "No attestation tag found" - - name: Verify with policy - run: | - ./blob verify \ - --policy .github/policies/src-image.yaml \ - "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" + go install oras.land/oras/cmd/oras@latest + + echo "Checking for attestation at tag: $TAG.att" + if oras manifest fetch "${{ env.IMAGE_NAME }}:$TAG.att" > /dev/null 2>&1; then + echo "✓ SLSA attestation found" + + # Verify it's a valid SLSA provenance attestation + PRED_TYPE=$(oras manifest fetch "${{ env.IMAGE_NAME }}:$TAG.att" 2>/dev/null | jq -r '.layers[0].annotations.predicateType // empty') + if [[ "$PRED_TYPE" == "https://slsa.dev/provenance/v0.2" ]]; then + echo "✓ Valid SLSA provenance v0.2 attestation" + else + echo "✗ Unexpected predicate type: $PRED_TYPE" + exit 1 + fi + else + echo "✗ SLSA attestation not found" + exit 1 + fi + + echo "" + echo "=== Verification complete ===" + echo "Archive: ${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" + echo "- Sigstore signature: ✓" + echo "- SLSA provenance: ✓" From 5013b12b69e4a8a7e889feaa74ee2a0ce5de3733 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 21:14:31 -0800 Subject: [PATCH 6/8] fix(ci): use actions/attest-build-provenance for SLSA attestations Replace slsa-github-generator with actions/attest-build-provenance which pushes attestations to the registry using OCI referrers API. This allows the blob library to discover and verify attestations properly. Changes: - Use attest-build-provenance with push-to-registry: true - Add attestations: write permission - Simplify verify step to use blob verify with policy - Update policy to verify SLSA provenance only - Use go-version-file for consistency Co-Authored-By: Claude Opus 4.5 --- .github/policies/src-image.yaml | 10 +---- .github/workflows/publish-src.yml | 69 +++++++++---------------------- 2 files changed, 22 insertions(+), 57 deletions(-) diff --git a/.github/policies/src-image.yaml b/.github/policies/src-image.yaml index 5c64830..53cd540 100644 --- a/.github/policies/src-image.yaml +++ b/.github/policies/src-image.yaml @@ -1,14 +1,8 @@ # Policy for verifying the blob-cli source archive # -# This policy is intended for use with `blob verify` once tag-based attestation -# discovery is supported. Currently, the SLSA generator uses tag-based storage -# (sha256-.att) which the blob CLI doesn't yet discover. -# -# The workflow validates attestations manually using oras until this is implemented. -# -# TODO: Enable policy verification once blob library supports tag-based attestation discovery +# This policy verifies SLSA provenance attestations generated by +# actions/attest-build-provenance and pushed to the registry. provenance: slsa: repository: meigma/blob-cli - branch: master diff --git a/.github/workflows/publish-src.yml b/.github/workflows/publish-src.yml index 2b54a45..1364531 100644 --- a/.github/workflows/publish-src.yml +++ b/.github/workflows/publish-src.yml @@ -10,7 +10,8 @@ on: permissions: contents: read packages: write - id-token: write # Required for Sigstore keyless signing and SLSA + id-token: write # Required for Sigstore keyless signing + attestations: write # Required for GitHub attestations env: IMAGE_NAME: ghcr.io/meigma/blob-cli/src @@ -25,14 +26,14 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: - go-version: "1.25" + go-version-file: go.mod cache: true - name: Build blob CLI @@ -55,36 +56,28 @@ jobs: echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" echo "Pushed with digest: ${DIGEST}" - provenance: - name: Generate SLSA Provenance - needs: [build-and-push] - permissions: - actions: read - id-token: write - packages: write - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 - with: - image: ghcr.io/meigma/blob-cli/src - digest: ${{ needs.build-and-push.outputs.digest }} - registry-username: ${{ github.actor }} - secrets: - registry-password: ${{ secrets.GITHUB_TOKEN }} + - name: Generate SLSA provenance attestation + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v2.3.0 + with: + subject-name: ${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true verify: name: Verify Archive - needs: [build-and-push, provenance] + needs: [build-and-push] runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: - go-version: "1.25" + go-version-file: go.mod cache: true - name: Build blob CLI @@ -97,38 +90,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Verify archive and attestations + - name: Verify archive with policy run: | echo "=== Inspecting archive ===" ./blob inspect "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" echo "" - echo "=== Verifying SLSA attestation exists ===" - # The SLSA generator uses tag-based storage for attestations - DIGEST="${{ needs.build-and-push.outputs.digest }}" - TAG="${DIGEST//:/\-}" - - go install oras.land/oras/cmd/oras@latest - - echo "Checking for attestation at tag: $TAG.att" - if oras manifest fetch "${{ env.IMAGE_NAME }}:$TAG.att" > /dev/null 2>&1; then - echo "✓ SLSA attestation found" - - # Verify it's a valid SLSA provenance attestation - PRED_TYPE=$(oras manifest fetch "${{ env.IMAGE_NAME }}:$TAG.att" 2>/dev/null | jq -r '.layers[0].annotations.predicateType // empty') - if [[ "$PRED_TYPE" == "https://slsa.dev/provenance/v0.2" ]]; then - echo "✓ Valid SLSA provenance v0.2 attestation" - else - echo "✗ Unexpected predicate type: $PRED_TYPE" - exit 1 - fi - else - echo "✗ SLSA attestation not found" - exit 1 - fi + echo "=== Verifying with policy ===" + ./blob verify \ + --policy .github/policies/src-image.yaml \ + "${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" echo "" echo "=== Verification complete ===" - echo "Archive: ${{ env.IMAGE_NAME }}@${{ needs.build-and-push.outputs.digest }}" - echo "- Sigstore signature: ✓" - echo "- SLSA provenance: ✓" From b9facb0e7fd8cffd54184a788d13fd1f7fd48eb8 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 21:17:37 -0800 Subject: [PATCH 7/8] chore(ci): revert trigger to push on master only Testing complete - workflow successfully builds, signs, and verifies the source archive with SLSA provenance. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/publish-src.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/publish-src.yml b/.github/workflows/publish-src.yml index 1364531..c3e654f 100644 --- a/.github/workflows/publish-src.yml +++ b/.github/workflows/publish-src.yml @@ -1,11 +1,8 @@ name: Publish Source Archive on: - pull_request: + push: branches: [master] - # TODO: Revert to push-only after testing - # push: - # branches: [master] permissions: contents: read From bcf4207fd8e14ad093a51d3effcc119120e30ea3 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 21:34:32 -0800 Subject: [PATCH 8/8] feat(policy): add comprehensive verification policy The policy now verifies: 1. Sigstore signature: Must be from the publish-src workflow on master 2. SLSA provenance: Must be from meigma/blob-cli on master branch This ensures the source archive was built by the official CI workflow from the master branch. Co-Authored-By: Claude Opus 4.5 --- .github/policies/src-image.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/policies/src-image.yaml b/.github/policies/src-image.yaml index 53cd540..e053515 100644 --- a/.github/policies/src-image.yaml +++ b/.github/policies/src-image.yaml @@ -1,8 +1,15 @@ # Policy for verifying the blob-cli source archive # -# This policy verifies SLSA provenance attestations generated by -# actions/attest-build-provenance and pushed to the registry. +# This policy verifies: +# 1. Sigstore signature from the publish-src workflow on master +# 2. SLSA provenance from the meigma/blob-cli repository on master + +signature: + keyless: + issuer: https://token.actions.githubusercontent.com + identity: https://github.com/meigma/blob-cli/.github/workflows/publish-src.yml@refs/heads/master provenance: slsa: repository: meigma/blob-cli + branch: refs/heads/master