8. GoReleaser Release Pipeline, SHA-Pinned Actions, and Signed Artifacts
Status
Accepted
Context
The release pipeline was hand-rolled. make release shelled out to a for loop
of go build invocations for each GOOS/GOARCH, cyclonedx-gomod produced the
SBOM, a separate workflow job built and pushed the container image with
docker/build-push-action, and softprops/action-gh-release published the
GitHub Release. The SBOM was generated by a different tool (cyclonedx-gomod)
than the image SBOM (buildx/syft), so there were two SBOM definitions to keep in
sync. Adding artifact signing or a Homebrew formula would have meant bolting yet
more bespoke steps onto the Makefile and workflow.
Separately, every workflow referenced its GitHub Actions by mutable tag
(actions/checkout@v6, docker/login-action@v4, …). A mutable tag can be moved
to point at malicious code after review, so a tag-pinned workflow trusts every
future force-push to that tag — a well-known CI/CD supply-chain weakness.
Decision
Adopt GoReleaser (.goreleaser.yaml, schema v2) as the single release
engine, and pin all Actions to commit SHAs.
- One pipeline builds the binaries,
.tar.gzarchives, andchecksums.txt; generates a CycloneDX SBOM per archive via syft (replacingcyclonedx-gomod, making syft the single SBOM source); builds the multi-archghcr.io/fjacquet/pstore_exporterimage via thedockers_v2(buildx) pipe withsbom: truefor SBOM + provenance attestations; signschecksums.txtwith keyless cosign (Sigstore bundle) over GitHub OIDC; and publishes a Homebrew cask tofjacquet/homebrew-tap. - A dedicated
Dockerfile.goreleasercopies the pre-built per-platform binary (${TARGETPLATFORM}/pstore_exporter) instead of recompiling; the multi-stage./Dockerfileis retained for local/dev and Docker Compose builds. - All GitHub Actions are pinned to full commit SHAs with a
# vXcomment inci.yml,release.yml, anddocs.yml. Thesemgrep/semgrepscanner container is deliberately left on a rolling tag so it runs current rules. - CI regenerates the SBOMs in snapshot mode (
make sbom→goreleaser release --snapshot --skip=publish,sign,docker,homebrew) so the PR-time SBOM uses the exact same definition as a real release.
Consequences
- One declarative config replaces the cross-compile loop, the separate SBOM step,
and the separate image job. Release assets are now
.tar.gzarchives rather than raw binaries. - The release SBOM and the image SBOM both come from syft — a single source of
truth.
make toolsno longer installscyclonedx-gomod. - Artifacts are verifiable:
cosign verify-blob --bundle checksums.txt.sigstore.json checksums.txt, thensha256sum -c checksums.txt. - New required secret:
HOMEBREW_TAP_GITHUB_TOKEN(a PAT withcontents:writeon the tap repo; the defaultGITHUB_TOKENcannot push to another repository). The release job also needspackages: writeandid-token: writepermissions. - Pinned SHAs must be bumped deliberately (e.g. Dependabot/Renovate keyed on the
# vXcomment) rather than drifting with upstream tags — the intended trade-off of supply-chain integrity over automatic updates. - Local release tooling (
goreleaser,syft,cosign) is now a prerequisite formake sbom/make release; install viabrew install goreleaser syft cosign. CI provides them through pinned actions.