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.