From d0984e1aa590da1b594ec587ffc4c2c5f6487ed6 Mon Sep 17 00:00:00 2001 From: Georgii Sopin <73875759+bloogefest@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:47:56 +0300 Subject: [PATCH] feat: attach signed source archive to releases --- .github/workflows/release.yml | 46 +++++++++++++++++++++++++++++++++++ README.md | 9 ++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f73435..20d458d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,3 +25,49 @@ jobs: token: ${{ steps.app.outputs.token }} config-file: release-please-config.json manifest-file: .release-please-manifest.json + # Source archive of the release with signed SLSA build provenance (OpenSSF Scorecard Signed-Releases). + provenance: + needs: release-please + if: needs.release-please.outputs.released == 'true' + runs-on: ubuntu-latest + permissions: + attestations: write + contents: read + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + ref: ${{ needs.release-please.outputs.tag }} + - name: Source archive and checksums + id: archive + env: + REPO: ${{ github.event.repository.name }} + VERSION: ${{ needs.release-please.outputs.version }} + run: | + name="$REPO-$VERSION" + git archive --format=tar.gz --prefix="$name/" -o "$name.tar.gz" HEAD + sha256sum "$name.tar.gz" > SHA256SUMS + echo "name=$name" >> "$GITHUB_OUTPUT" + - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + id: attest + with: + subject-path: ${{ steps.archive.outputs.name }}.tar.gz + - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3 + id: app + with: + app-id: ${{ vars.RELEASE_APP_ID }} + permission-contents: write + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + repositories: ${{ github.event.repository.name }} + - name: Upload to the release + env: + BUNDLE: ${{ steps.attest.outputs.bundle-path }} + GH_TOKEN: ${{ steps.app.outputs.token }} + NAME: ${{ steps.archive.outputs.name }} + TAG: ${{ needs.release-please.outputs.tag }} + run: | + cp "$BUNDLE" "$NAME.tar.gz.sigstore.json" + jq -c '.dsseEnvelope' "$BUNDLE" > "$NAME.tar.gz.intoto.jsonl" + gh release upload "$TAG" --clobber -R "$GITHUB_REPOSITORY" \ + "$NAME.tar.gz" "$NAME.tar.gz.sigstore.json" "$NAME.tar.gz.intoto.jsonl" SHA256SUMS diff --git a/README.md b/README.md index 68246a8..eb57e01 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ After each release Renovate opens a pull request in every repository that uses t | `docs-lint.yml` | markdownlint, REUSE compliance, required files, README section order | | `lint-iac.yml` | yamllint, gitleaks and Trivy config scan | | `pr-title.yml` | Conventional Commits check of the pull request title | -| `release.yml` | release-please through the `sogeor-release-bot` GitHub App; outputs `released`, `tag`, `version` | +| `release.yml` | release-please through the `sogeor-release-bot` GitHub App; attaches a source archive with signed SLSA build provenance to each release; outputs `released`, `tag`, `version` | | `tofu-deploy.yml` | OpenTofu plan with read-only credentials, then apply of that saved plan after approval in the `production` environment | | `tofu-plan-pr.yml` | OpenTofu plan for pull requests from branches of the repository, posted as a comment | @@ -59,10 +59,17 @@ jobs: lint: uses: sogeor/workflows/.github/workflows/docs-lint.yml@ # v1.2.0 release: + permissions: { attestations: write, contents: read, id-token: write } uses: sogeor/workflows/.github/workflows/release.yml@ # v1.2.0 secrets: inherit ``` +The `release` job needs `attestations: write` and `id-token: write`: a reusable workflow cannot get more permissions than its caller. Each release gets `-.tar.gz`, `SHA256SUMS`, the Sigstore bundle `*.sigstore.json` and the in-toto envelope `*.intoto.jsonl`. Verify an archive with: + +```bash +gh attestation verify -.tar.gz -R sogeor/ +``` + Write the release tag first (`@v1.2.0`), then pin it with `sogeor-pin-actions` or let Renovate open the pinning pull request; never leave a tag in `uses:`. Use the `release` outputs to run publishing jobs only when a release was created: