From 0459b5e4f46b181fe904d6ca0f0a7152fe57d32e Mon Sep 17 00:00:00 2001 From: Robert McConnell Date: Fri, 18 Sep 2026 13:35:51 +0000 Subject: [PATCH] ci: attest release source archives --- .../workflows/publish-release-artifacts.yml | 64 +++++++++++++++++++ .github/workflows/windows-artifacts.yml | 20 +++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-release-artifacts.yml b/.github/workflows/publish-release-artifacts.yml index 17e3a056fe3..9815ffe7188 100644 --- a/.github/workflows/publish-release-artifacts.yml +++ b/.github/workflows/publish-release-artifacts.yml @@ -11,6 +11,8 @@ jobs: publish-release-artifacts: permissions: contents: write # to fetch code and upload artifacts + id-token: write # to sign release attestations + attestations: write # to store release attestations runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') @@ -20,6 +22,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 - name: Archive + id: archive env: RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }} RELEASE_SIGNING_KEY_PASSPHRASE: ${{ secrets.RELEASE_SIGNING_KEY_PASSPHRASE }} @@ -34,6 +37,7 @@ jobs: # Otherwise, degrade to using full tag. export VERSION="$(echo "$TAG" | sed 's_^v\([0-9]\+\.[0-9]\+\.[0-9]\+\)$_\1_')" export ZSTD_VERSION="zstd-$VERSION" + echo "zstd_version=$ZSTD_VERSION" >> "$GITHUB_OUTPUT" # archive git archive $TAG \ @@ -65,6 +69,66 @@ jobs: gpg $GPG_BATCH_OPTS --armor --sign --sign-with signing@zstd.net --detach-sig --passphrase "$RELEASE_SIGNING_KEY_PASSPHRASE" --output $ZSTD_VERSION.tar.gz.sig $ZSTD_VERSION.tar.gz fi + - name: Generate gzip SBOM + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # tag=v0.24.0 + with: + file: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.gz + format: cyclonedx-json + output-file: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.gz.cdx.json + syft-version: v1.52.0 + dependency-snapshot: false + upload-artifact: false + upload-release-assets: false + + - name: Generate zstd SBOM + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # tag=v0.24.0 + with: + file: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.zst + format: cyclonedx-json + output-file: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.zst.cdx.json + syft-version: v1.52.0 + dependency-snapshot: false + upload-artifact: false + upload-release-assets: false + + - name: Validate SBOM identity + env: + ZSTD_VERSION: ${{ steps.archive.outputs.zstd_version }} + run: | + set -euo pipefail + for extension in tar.gz tar.zst; do + archive="$ZSTD_VERSION.$extension" + expected="sha256:$(cut -d' ' -f1 "artifacts/$archive.sha256")" + sbom="artifacts/$archive.cdx.json" + jq -e --arg archive "$archive" --arg expected "$expected" ' + .bomFormat == "CycloneDX" + and .specVersion == "1.7" + and .metadata.component.type == "file" + and .metadata.component.name == $archive + and .metadata.component.version == $expected + and ((.components | length) > 0) + ' "$sbom" >/dev/null + done + + - name: Attest source archive provenance + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # tag=v3 + with: + subject-path: | + artifacts/${{ steps.archive.outputs.zstd_version }}.tar.gz + artifacts/${{ steps.archive.outputs.zstd_version }}.tar.zst + + - name: Attest gzip SBOM + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # tag=v4 + with: + subject-path: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.gz + sbom-path: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.gz.cdx.json + + - name: Attest zstd SBOM + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # tag=v4 + with: + subject-path: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.zst + sbom-path: artifacts/${{ steps.archive.outputs.zstd_version }}.tar.zst.cdx.json + - name: Publish uses: skx/github-action-publish-binaries@b9ca5643b2f1d7371a6cba7f35333f1461bbc703 # tag=release-2.0 env: diff --git a/.github/workflows/windows-artifacts.yml b/.github/workflows/windows-artifacts.yml index 1b035966968..f56bc887ae8 100644 --- a/.github/workflows/windows-artifacts.yml +++ b/.github/workflows/windows-artifacts.yml @@ -116,4 +116,22 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release upload "${{ github.ref_name }}" "$env:GITHUB_WORKSPACE/zstd-${{ github.ref_name }}-${{ matrix.ziparch }}.zip" --clobber \ No newline at end of file + $file = "$env:GITHUB_WORKSPACE/zstd-${{ github.ref_name }}-${{ matrix.ziparch }}.zip" + $name = Split-Path -Leaf $file + $localDigest = "sha256:" + (Get-FileHash -Algorithm SHA256 $file).Hash.ToLower() + + $release = gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/${{ github.ref_name }}" | ConvertFrom-Json + $asset = $release.assets | Where-Object { $_.name -eq $name } + + if ($null -ne $asset) { + if (-not $asset.digest) { + throw "Existing release asset $name has no recorded SHA-256 digest; refusing to overwrite it" + } + if ($asset.digest -ne $localDigest) { + throw "Existing release asset $name differs from the generated artifact; refusing to overwrite it" + } + Write-Host "$name already exists with the generated digest; leaving it unchanged" + exit 0 + } + + gh release upload "${{ github.ref_name }}" $file \ No newline at end of file