Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,11 @@ jobs:
permission-pull-requests: write

- name: Mint release token
# Used as GoReleaser's GITHUB_TOKEN to publish the release to this repo.
# The default GITHUB_TOKEN cannot be used here: GitHub does not start new
# workflow runs from events triggered by the default token, so a release
# published with it would never fire the `release: published` trigger on
# "Release lifecycle sync" (notify-landing-yank.yml) — and the
# landing-page docs sync (repository_dispatch) would silently never run.
# An app token is attributed to the app, so the publish event cascades.
# Used as GoReleaser's GITHUB_TOKEN to create the draft release on this
# repo and upload its assets (archives, packages, checksums, signature).
# The release stays a draft here (see .goreleaser.yaml) — actual
# publishing happens in the `publish` job below, after the SLSA
# provenance file is also attached.
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: release-token
with:
Expand Down Expand Up @@ -118,7 +116,45 @@ jobs:
# shorter tag — slsa-verifier resolves the builder identity from this exact ref,
# and SHA-pinning breaks that resolution. See .github/pinact.yaml, which excludes
# this line from automated re-pinning.
#
# Uploads to the still-draft release created above — draft releases are
# exempt from GitHub's immutable-releases restriction, which otherwise
# rejects new assets on an already-published release.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: ${{ needs.release.outputs.hashes }}
upload-assets: ${{ startsWith(github.ref, 'refs/tags/') }}

publish:
needs: [release, provenance]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Mint release token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: release-token
with:
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
owner: bomly-dev
repositories: bomly-cli
permission-contents: write

- name: Publish release
# Finalizes the draft into a published release now that every asset —
# GoReleaser's archives/packages/checksums/signature, plus the SLSA
# provenance file from the `provenance` job — is attached. Must run
# last: immutable releases reject new assets once published.
#
# Uses the release-bot app token, not the default GITHUB_TOKEN: GitHub
# does not start new workflow runs from events triggered by the
# default token, so a release published with it would never fire the
# `release: published` trigger on "Release lifecycle sync"
# (notify-landing-yank.yml) — and the landing-page docs sync would
# silently never run. An app token is attributed to the app, so the
# publish event cascades.
env:
GH_TOKEN: ${{ steps.release-token.outputs.token }}
RELEASE_TAG: ${{ github.ref_name }}
run: gh release edit "$RELEASE_TAG" --draft=false --repo bomly-dev/bomly-cli
7 changes: 6 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ release:
github:
owner: bomly-dev
name: bomly-cli
draft: false
# Stays a draft until the `publish` job in release.yml finalizes it. GitHub's
# immutable releases feature forbids adding assets once a release is
# published, and the SLSA provenance file is attached by a later job — so
# publishing has to be the last step, after every asset (including
# provenance) is in place. See dev-docs/CI.md.
draft: true
prerelease: auto
footer: |
---
Expand Down
19 changes: 10 additions & 9 deletions dev-docs/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Bomly dogfoods its own domain by tracking the project's [OpenSSF Scorecard](http
- **Token-Permissions** — every workflow declares a top-level `permissions:` block scoped to `contents: read`. Any write scope (release publishing, the Guard PR comment, the smoke-goldens PR) is granted at the **job** level only, never at the top level.
- **Pinned-Dependencies** — all GitHub Actions are pinned by full commit SHA with a trailing `# vX.Y.Z` comment (for example `actions/checkout@<sha> # v7.0.0`). Dependabot's `github-actions` updater understands this form and bumps both the SHA and the comment, so pinning does not freeze us on stale actions. When adding a new `uses:`, pin it the same way — `pinact run` (suzuki-shunsuke/pinact) rewrites the whole tree, or resolve a single tag with `gh api repos/<owner>/<repo>/commits/<tag> --jq .sha`. The `Smoke` and `Update Smoke Goldens` workflows install `pip`/`pipenv`/`poetry` from `.github/requirements-ci-tools.txt`, a hash-locked, fully-resolved requirements file (`pip install --require-hashes`) instead of an unpinned inline `pip install`. Regenerate it after bumping `.github/requirements-ci-tools.in` with `pip-compile --allow-unsafe --generate-hashes --output-file=requirements-ci-tools.txt requirements-ci-tools.in` run from `.github/` under the same Python version the workflows use (3.12), so the resolved hash set covers the right wheel tags.
- **SAST** — CodeQL runs on every push, PR, and weekly.
- **Signed-Releases** — the `release` job signs `SHA256SUMS` keylessly with [cosign](https://docs.sigstore.dev/cosign/signing/overview/) (`SHA256SUMS.sigstore.json`, GitHub OIDC identity, no managed keys), and a separate `provenance` job calls the [slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) generic builder to produce a single `multiple.intoto.jsonl` SLSA Build Level 3 provenance file over every release artifact's hash, uploaded to the same GitHub release. Verification commands for end users are in [docs/INSTALLATION.md](../docs/INSTALLATION.md#verify-release-checksums). The generator's `uses:` line is pinned to the `v2.1.0` tag, not a commit SHA — SHA-pinning it breaks `slsa-verifier`'s builder-identity check — and `.github/pinact.yaml` excludes that line from automated re-pinning so it doesn't regress.
- **Signed-Releases** — the `release` job signs `SHA256SUMS` keylessly with [cosign](https://docs.sigstore.dev/cosign/signing/overview/) (`SHA256SUMS.sigstore.json`, GitHub OIDC identity, no managed keys), and a separate `provenance` job calls the [slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) generic builder to produce a single `multiple.intoto.jsonl` SLSA Build Level 3 provenance file over every release artifact's hash, uploaded to the same GitHub release. Verification commands for end users are in [docs/INSTALLATION.md](../docs/INSTALLATION.md#verify-release-checksums). The generator's `uses:` line is pinned to the `v2.1.0` tag, not a commit SHA — SHA-pinning it breaks `slsa-verifier`'s builder-identity check — and `.github/pinact.yaml` excludes that line from automated re-pinning so it doesn't regress. Because the provenance file is attached by a job downstream of `release`, and GitHub's immutable releases feature blocks adding assets after a release is published, `.goreleaser.yaml` keeps the release as a draft and a final `publish` job flips it to published only after `provenance` succeeds — see [Release Process](#release-process).

A few Scorecard checks require maintainer action **outside** the repository and are not code changes:

Expand Down Expand Up @@ -176,8 +176,8 @@ go build -tags "bomly_external_syft,bomly_external_grype" -o bin/bomly-lite ./cm

Release packaging is driven by `.goreleaser.yaml`. The release workflow uses GoReleaser to create:

- A published GitHub Release with archives for `bomly` and `bomly-lite`.
- `SHA256SUMS`.
- A GitHub Release with archives for `bomly` and `bomly-lite`.
- `SHA256SUMS`, keylessly signed with cosign (`SHA256SUMS.sigstore.json`).
- Linux `.deb`, `.rpm`, `.apk`, and Arch Linux package artifacts for the full `bomly` binary.
- Homebrew cask, Scoop, and WinGet manifest pull requests.

Expand All @@ -186,20 +186,21 @@ Release packaging is driven by `.goreleaser.yaml`. The release workflow uses GoR
1. Merge to `main`.
2. When ready to publish, a maintainer runs the `Auto Version` workflow from `main` and chooses a `patch`, `minor`, or `major` bump.
3. The `Auto Version` workflow updates `cmd/bomly/main.go`, commits the bump, creates a tag such as `v0.2.0`, and starts the `Release` workflow.
4. The `Release` workflow reruns validation and runs GoReleaser.
4. The `Release` workflow reruns validation, then the `release` job runs GoReleaser.
5. GoReleaser cross-compiles `bomly` and `bomly-lite`, packages archives for:
- `linux/amd64`
- `linux/arm64`
- `darwin/amd64`
- `darwin/arm64`
- `windows/amd64`
- `windows/arm64`
6. GoReleaser generates `SHA256SUMS` and Linux packages.
7. GoReleaser publishes the GitHub Release, using the configured GoReleaser header plus GitHub-native generated release notes, and uploads archives, packages, and checksums.
8. GoReleaser opens or updates package-manager manifest PRs for Homebrew, Scoop, and WinGet.
9. After the release is published, the `Release lifecycle sync` workflow dispatches the landing-page docs and changelog sync with the published timestamp.
6. GoReleaser generates `SHA256SUMS`, Linux packages, and the cosign signature, then creates the GitHub Release **as a draft** and uploads everything to it.
7. GoReleaser opens or updates package-manager manifest PRs for Homebrew, Scoop, and WinGet (these reference the release's download URLs, which aren't publicly fetchable until the release is published in step 9 — a brief window, typically under a minute).
8. The `provenance` job calls `slsa-github-generator` to attach SLSA provenance (`multiple.intoto.jsonl`) to the same draft release.
9. The `publish` job flips the release from draft to published, using the configured GoReleaser header plus GitHub-native generated release notes.
10. After the release is published, the `Release lifecycle sync` workflow dispatches the landing-page docs and changelog sync with the published timestamp.

The manual approval point for a release is the `Auto Version` workflow that creates the release tag. The GitHub Release is intentionally published automatically after validation so package-manager manifest PRs can reference public release assets and checksums.
The manual approval point for a release is the `Auto Version` workflow that creates the release tag. The GitHub Release stays a draft until every asset — including SLSA provenance — is attached, then a final job publishes it. This is required by GitHub's [immutable releases](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases) feature: once a release is published, no further assets can be added by anyone, so the provenance file (generated by a separate downstream job, by design — see [Supply-Chain Hardening](#supply-chain-hardening-openssf-scorecard)) must land before publish, not after.

## Yanking Releases

Expand Down