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
8 changes: 4 additions & 4 deletions .github/zizmor.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
rules:
superfluous-actions:
ignore:
# release-rust.yml:250 — dtolnay/rust-toolchain: explicit toolchain +
# release-rust.yml:257 — dtolnay/rust-toolchain: explicit toolchain +
# cross-target install; hand-rolling rustup here would just reimplement
# this action with less auditability.
- release-rust.yml:250
- release-rust.yml:257
stale-action-refs:
ignore:
# release-rust.yml:250 — dtolnay/rust-toolchain intentionally ships no
# release-rust.yml:257 — dtolnay/rust-toolchain intentionally ships no
# tags: master/stable/beta/nightly are branch aliases by design, so
# this pin is the tip of "master", not a stale reference.
- release-rust.yml:250
- release-rust.yml:257
Comment thread
coderabbitai[bot] marked this conversation as resolved.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
!.zed/settings.json
!CODE_OF_CONDUCT.md
!CODEOWNERS
!docs/*.md
!flake.lock
!flake.nix
!LICENSE
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
# Release Workflows

[![MIT](https://img.shields.io/badge/MIT-gray?logo=github&logoColor=white)](LICENSE)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/purpleclay/release-workflows/badge)](https://scorecard.dev/viewer/?uri=github.com/purpleclay/release-workflows)
Reusable release workflows for `purpleclay` projects. Every release built here ships with SLSA Build Level 3 provenance, per-artifact SBOMs, and keyless Sigstore signatures — with no secrets, no key material, and no per-project security plumbing.

Reusable release workflows for projects. Every release built here ships with SLSA Build Level 3 provenance and keyless Sigstore signatures — no per-project secrets or signing-key management. Per-artifact SBOM attestation is on the roadmap.

## Why this repository exists

GitHub artifact attestations generated inside a project's own workflow reach SLSA Build **Level 2**: the provenance is real, but it is produced by the same workflow a compromised repository could edit. Level **3** requires the provenance to be generated somewhere the build cannot reach — a shared, vetted, isolated workflow whose identity a tenant build cannot impersonate.
This repository is that workflow. Projects delegate their release to it with a single `uses:` call, and consumers gain something stronger than "this artifact has provenance": they can verify that a release was built by *this specific pipeline*, at a known commit, from a known source revision — and reject anything that wasn't.

This repository is that workflow. Projects delegate their release to it with a single `uses:` call, and consumers gain something stronger than "this artifact has provenance": they can verify that a release was built by _this specific pipeline_, at a known commit, from a known source revision — and reject anything that wasn't.

Centralising the release path has a second benefit that has nothing to do with attestations: there is exactly one place where release security is implemented, reviewed, and improved. A hardening change lands here once and every project inherits it on its next release.

## Workflows

| Workflow | Purpose | Caller contract |
| -------------------------------------------------- | ---------------------------------------- | -------------------------------------------- |
| [release-rust](.github/workflows/release-rust.yml) | Build, attest, and release Rust binaries | [docs/release-rust.md](docs/release-rust.md) |

The contract documents define everything callers may rely on — usage, inputs, outputs, supported targets, archive naming, attestation subjects, adoption steps, and how to verify what was produced — and change only under the versioning rules in [RELEASE.md](RELEASE.md).

Pin the full commit SHA of whichever workflow you adopt, with the version as a trailing comment, so your dependency-update tooling (Renovate, Dependabot, ...) can propose bumps — there are no floating major tags here, by design.
107 changes: 107 additions & 0 deletions docs/release-rust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# release-rust

Builds Rust binaries across a fixed target allowlist, attests them, and publishes a GitHub release. This page is the **caller contract** — anything listed here changes only under the versioning rules in [RELEASE.md](../RELEASE.md). The workflow file's comments explain _how_; this page defines _what_ callers may rely on.

## Usage

```yaml
name: release
on:
push:
tags: ["*.*.*"]

permissions: {}

jobs:
release:
permissions:
id-token: write # OIDC identity for Sigstore signing
attestations: write # persist attestations
contents: write # create the release, upload assets
actions: read # list this run's artifacts to resolve their immutable ids
uses: purpleclay/release-workflows/.github/workflows/release-rust.yml@<pinned-sha> # vX.Y.Z
with:
bin: gpg-import
targets: '["x86_64-unknown-linux-musl","aarch64-unknown-linux-musl","x86_64-apple-darwin","aarch64-apple-darwin"]'
```

Must be triggered from a **tag push**; the workflow fails fast otherwise.

## Inputs

See [release-rust.yml](../.github/workflows/release-rust.yml)

```yaml
uses: purpleclay/release-workflows/.github/workflows/release-rust.yml@<pinned-sha> # vX.Y.Z
with:
# Binary to build and package.
# Required.
bin:

# JSON array of target triples. Non-array input, empty arrays, duplicates,
# and unsupported targets all fail in the plan job.
# Required.
targets:

# Rust toolchain specifier.
# Optional. Default is stable
toolchain:

# GitHub environment for the attest/publish job. Auto-created unprotected
# on first reference — add a required reviewer per repo to complete the
# S11 control.
# Optional. Default is release
environment:

# Extra files bundled into each archive. Missing files are skipped; a
# file whose basename equals `bin` fails.
# Optional. Default is "LICENSE README.md"
package-files:
```

## Outputs

| Output | Description |
| ------------- | ----------------------------- |
| `release-url` | URL of the published release. |

## Supported targets

| Target | Runner | Build |
| ---------------------------- | ------------ | ---------------------- |
| `x86_64-unknown-linux-musl` | ubuntu-24.04 | cargo-zigbuild (cross) |
| `aarch64-unknown-linux-musl` | ubuntu-24.04 | cargo-zigbuild (cross) |
| `x86_64-apple-darwin` | macos-15 | native cargo |
| `aarch64-apple-darwin` | macos-15 | native cargo |

Adding a target is a minor version; removing one is a major.

## What a release contains

For each target: `<bin>-<version>-<target>.tar.gz` containing the binary plus `package-files`, where `<version>` is the tag name with `/` replaced by `-`. Plus `checksums.txt` (SHA-256 over each archive; generated before it exists, so it does not hash itself). Every asset — checksums.txt included — is a subject of a SLSA build provenance attestation signed by this workflow's identity. Release notes are generated by release-note from conventional commits. The archive naming and attestation subjects are contract: parsers (installers, download actions) may rely on them.

## What callers must know

- **Builds are clean-room**: no caches are used in the release path. Release builds are slower than CI builds; that is the price of the integrity claim.
- **Checkout is shallow** in the build job: build scripts must not derive version information from git history (vergen-style `git describe`). The archive and checksum names are derived entirely from the tag (`github.ref_name`), not from `Cargo.toml` or `CARGO_PKG_VERSION` — if your build embeds a version at compile time, keep it in sync with the release tag yourself.
- **One release per tag, ever.** Re-running the workflow against a tag that already has a published release fails. Fix-forward with a new tag.
- **Dependencies are pinned centrally** (Zig, cargo-zigbuild, all actions) and bumped via reviewed `fix(deps)` patch releases — callers inherit them by bumping their pinned SHA, normally via Renovate.

## Verifying what it produced

```sh
gh attestation verify <artifact>.tar.gz \
--repo purpleclay/<project> \
--signer-workflow purpleclay/release-workflows/.github/workflows/release-rust.yml
```

The `--signer-workflow` check is the SLSA Build L3 claim: the signing identity belongs to this reusable workflow, which the calling repository invokes but cannot edit.

## Adoption checklist

1. Delete in-repo build/package/publish release jobs; add the caller above.
2. Create the environment named by `environment` (default `release`) and add a required reviewer.
3. Confirm the tag ruleset permits your release tag pattern and enable
immutable releases.
4. Cut a pre-release tag; run the verify command against every asset.
5. Add the standard verification section to the project README.