From 631f457f0cab88dd89011b22055d4c390bb5c67a Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 31 Aug 2026 22:46:36 +0200 Subject: [PATCH] attest the sdist provenance, attach the bundle to the release The release workflow now generates a build provenance attestation for the sdist, saying that it was built by this workflow from this repository. GitHub serves it via the attestations API, and the attestation bundle is attached to the GitHub release as borghash-X.Y.Z.tar.gz.jsonl, too, so that the sdist can also be verified against that file: gh attestation verify --owner borgbackup borghash-X.Y.Z.tar.gz --- .github/workflows/release.yml | 45 ++++++++++++++++++++++++++++++++--- README.rst | 14 +++++++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9cb6a03..a1f3c94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,11 @@ # The GitHub release is created as a *draft* on purpose: the release notes want # a human. Publishing the draft is a single click in the GitHub UI. # +# The sdist gets a build provenance attestation, which says that this workflow +# built it from this repository at this commit. GitHub serves it via the +# attestations API, and the attestation bundle is attached to the release as a +# .jsonl file as well, so that the sdist can also be verified against a file. +# # The upload to PyPI is a separate job only so that the "pypi" environment gate # applies to the upload alone - that is the last chance to stop a release before # the irreversible step. @@ -42,7 +47,9 @@ jobs: timeout-minutes: 30 permissions: - contents: write # to create the release + contents: write # to create the release + id-token: write # to attest the sdist + attestations: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -97,6 +104,27 @@ jobs: "$RUNNER_TEMP/venv-sdist/bin/pytest" -v -rs tests/ "$RUNNER_TEMP/venv-sdist/bin/borghash-demo" + - name: Attest the sdist provenance + # Only after the checks above: an attestation for a sdist that does not + # install would be a signed statement about a broken release. + id: attest + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: 'dist/*.tar.gz' + + - name: Keep the attestation bundle as a release asset + # "gh attestation verify" fetches the attestation from the GitHub API by + # default, but the bundle is tiny, so attach it to the release, too: then + # the sdist can also be verified against that file, via "--bundle". + env: + BUNDLE: ${{ steps.attest.outputs.bundle-path }} + TAG: ${{ github.ref_name }} + run: | + set -euxo pipefail + # ".jsonl" is how "gh attestation download" names such a file. + cp "$BUNDLE" "dist/borghash-$TAG.tar.gz.jsonl" + ls -l dist/ + - name: Create the draft release env: GH_TOKEN: ${{ github.token }} @@ -115,17 +143,28 @@ jobs: borghash is a Cython extension: installing the source distribution below needs a C compiler, but no Cython - the generated C files are included. + + ### Verification + + The sdist has a [build provenance attestation](https://github.com/borgbackup/borghash/attestations): + + \`gh attestation verify --owner borgbackup borghash-$TAG.tar.gz\` + + The attestation bundle is attached as \`borghash-$TAG.tar.gz.jsonl\`, too, for + verifying against that file instead of the GitHub API: + + \`gh attestation verify --owner borgbackup --bundle borghash-$TAG.tar.gz.jsonl borghash-$TAG.tar.gz\` EOF if gh release view "$TAG" > /dev/null 2>&1; then # a re-run of this job: keep the (possibly already edited) release and # just replace its assets. - gh release upload "$TAG" --clobber dist/*.tar.gz + gh release upload "$TAG" --clobber dist/*.tar.gz dist/*.jsonl else gh release create "$TAG" \ --draft $prerelease \ --title "borghash $TAG" \ --notes-file release-notes.md \ - dist/*.tar.gz + dist/*.tar.gz dist/*.jsonl fi gh release view "$TAG" --json isDraft,isPrerelease,assets diff --git a/README.rst b/README.rst index 20342d6..1f7edaf 100644 --- a/README.rst +++ b/README.rst @@ -186,10 +186,16 @@ an annotated, signed tag named like the version (no ``v`` prefix) onto the git push origin 0.3.0 Pushing the tag runs ``.github/workflows/release.yml``, which builds the sdist, -checks that it is complete and installable, and creates a *draft* GitHub -release with it. The upload to PyPI happens in the ``pypi`` job, which uses -trusted publishing (no API token) and waits for an approval if the ``pypi`` -environment has required reviewers configured. +checks that it is complete and installable, attests its build provenance and +creates a *draft* GitHub release with the sdist and the attestation bundle +(``borghash-0.3.0.tar.gz.jsonl``). The upload to PyPI happens in the ``pypi`` +job, which uses trusted publishing (no API token) and waits for an approval if +the ``pypi`` environment has required reviewers configured. + +The attestation says that this workflow built this sdist from this repository, +and is checked with:: + + gh attestation verify --owner borgbackup borghash-0.3.0.tar.gz Finally, write the release notes and publish the draft release.