Skip to content
Draft
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
20 changes: 20 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
Expand All @@ -70,6 +72,17 @@ jobs:
with:
go-version: "1.25"

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: Import GPG key
env:
GPG_KEY_MATERIAL: ${{ secrets.CASCADE_RELEASE_GPG_KEY }}
run: |
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
echo "$GPG_KEY_MATERIAL" | gpg --batch --import

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
with:
Expand All @@ -78,3 +91,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
GPG_FINGERPRINT: ${{ secrets.CASCADE_RELEASE_GPG_FINGERPRINT }}

- name: Attest build provenance
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: "dist/*"
github-token: ${{ secrets.GITHUB_TOKEN }}
45 changes: 43 additions & 2 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,61 @@ builds:
goarch:
- amd64
- arm64
flags:
- -trimpath
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -X main.date={{.CommitDate}}
mod_timestamp: '{{ .CommitTimestamp }}'

archives:
- id: archives
format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"

checksum:
name_template: "checksums.txt"

sboms:
- artifacts: archive
documents:
- "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom.spdx.json"

signs:
# cosign keyless signing (OIDC + sigstore)
- id: cosign
cmd: cosign
args:
- sign-blob
- --output-certificate=${certificate}
- --output-signature=${signature}
- ${artifact}
- --yes
artifacts: checksum
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
output: true

# GPG signing. The release key has no passphrase (it lives only as a repo
# secret, the same trust boundary a passphrase would), so no passphrase
# handling is needed.
- id: gpg
cmd: gpg
args:
- --batch
- --no-tty
- --local-user
- "{{ .Env.GPG_FINGERPRINT }}"
- --armor
- --output
- "${signature}"
- --detach-sign
- "${artifact}"
artifacts: checksum
signature: "${artifact}.asc"
output: true

changelog:
use: github
sort: asc
Expand Down
29 changes: 29 additions & 0 deletions docs/cascade-release-public-key.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGo/+XEBEADESe0XSTtG3VcsjrD7y8yXNlDYltHdkTlNDaS4hs2aNMoGYCN2
3+RGNDLOwVBD/mprWcXwZUauKYDnnrFF6wjLqg1jsEoWKLYOmCwJa/QhRFtof1+b
Xj9Vku5E83YGij9u+6TQdJd8dz5xT4yx/dd5dshbi1LXEj7REla/lxLBdqk4bz7O
zfilr6oBVVSsvmLhZBXpXgXIRO1ROs6Qc3r5W/qM6lCCMrpBA+7Mw+sCiOj2PIvJ
xEkN8qUeYF2lPvD2jdullh6Zp8klGlnZjPGhpzrSf1MTjlK0G5zs8EVCvwyvEUFe
935dwMkpJ3DcYTypyDgBDXZPXB7KX3Z/zWWMrlWknR4I4AarF+Jk9tMjcA6L2B0G
uGyZisVxylJdUlj2LesVBfgU6xCri96+hmCESIWnHnl/KYIVm/towrXUXxgs6TfZ
572nMI3kXZR9wCXRENh9PcBsNZl7YMYufSRbqKWL20XhoxqwdbqCTAZPdX9F+RWG
34ioUwS3ZXbOR6alRsHYyGdvsqzyIYf63P/6Mpz1ye3Byjq4IYgOoJwKkjWa1McW
ZSnqVB8TskGFi0LfCHrSJBgeDUHhoFSDhIztyT4B5WcpGKl6fAp9uq65t6BvII7e
eyZyAA3iN2mTma0T6bPUIw8KB3dgY2CG789bbNdzBruv6Ufk258RS8vCRwARAQAB
tC9DYXNjYWRlIFJlbGVhc2UgU2lnbmluZyA8aW5mb0BzdGFibGVrZXJuZWwuY29t
PokCUQQTAQgAOxYhBOdFYjn1Sn92xmIfvaqvEgknHbHaBQJqP/lxAhsDBQsJCAcC
AiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEKqvEgknHbHaBSUQAIHKg+wrfTfU7mfK
uwuPtjC6aYvhdW9vSxeSOfoT8BH2KKAU0W9oC8y5ixzeuVG4NG/4JSVRz5zwmOr8
EDy4d3f+P/qTR3l6hNR6iNyKPMtGqrnAr6w03fO1KG9jJwcelTTe158Az0PQWcx3
pBW7cL0s8Tci1JBo+942ILpf3aO5AIz8gN9mULsW6ZlX1lS7eTA2jUpEVvIZw4qJ
+PNr1z/c4eRUsseBCCwcqvSUbGp7Y11sVSjsA64s2Ysh8jBdeU8HxsYizWNrLdYN
qdbnXcc+Stlo0QreeJO0/uhz6Z/1omqJPGXtydZrp5XAArm/eUDY9xiHvnyihprL
WDWeB9A0WIcQDJm69GwhMhopOUYM3rySZfapPG/ycqXaHfLp6jZfcGZCWBUWpEmg
oRE/GhQ757bwrZyj9CTz0DWJtdMpaY8eRwC2w6O3jbDVNgFUHgO/c8xa7dGsvMZF
dPf/uAYEPC5V5SfIoa6bxBjvaMo1YlWMXRCYqvu5N+nCFNtiABTr8sLKEkn/AZiY
522BFyj/1zsvFBt3V5Q2m6jo59mIEab8E4Vu49s9qlGRVPK09TkqbZvgxuDwUR3U
cQOMJCFjXCa2AuAm/9KMuTlsEqOVqCbR2W9NULjXgHaFJMhGx2oY2lB9CLQif0cN
pQg9BtXEuAHx3zRLgAn+nDJr09uI
=SHi3
-----END PGP PUBLIC KEY BLOCK-----
109 changes: 109 additions & 0 deletions docs/release-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Release Verification Guide

cascade releases are signed using two cryptographic mechanisms and include SLSA build provenance to ensure authenticity and integrity.

## Verifying cosign signatures (recommended)

Cascade uses keyless cosign signing via Sigstore, which does not require key management. To verify a release:

1. Install cosign (https://github.com/sigstore/cosign/releases).

2. Download the release artifacts and signatures from the GitHub release page (checksums.txt, checksums.txt.sig, checksums.txt.pem, and the archives).

3. Verify the checksums file signature:

```bash
cosign verify-blob \
--certificate=checksums.txt.pem \
--signature=checksums.txt.sig \
--certificate-identity-regexp='^https://github.com/stablekernel/cascade' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
checksums.txt
```

The certificate is issued by Sigstore's public certificate authority. cosign automatically verifies the certificate chain and confirms the signature was created by GitHub Actions during the release workflow run.

4. Verify the checksums match the downloaded binaries:

```bash
sha256sum -c checksums.txt
```

## Verifying GPG signatures

For users who prefer traditional GPG verification:

1. Obtain the cascade maintainer's public key from `docs/cascade-release-public-key.asc` in this repository and import it:

```bash
gpg --import docs/cascade-release-public-key.asc
```

2. Download the release artifacts and `.asc` signature files from the GitHub release page.

3. Verify the checksums file signature:

```bash
gpg --verify checksums.txt.asc checksums.txt
```

4. If verification succeeds, verify the checksums match the downloaded binaries:

```bash
sha256sum -c checksums.txt
```

## Verifying SLSA provenance

Cascade releases include SLSA build provenance that provides cryptographic evidence about how the artifacts were built. The provenance is stored in GitHub's attestation store and verified with the GitHub CLI. To verify:

1. Install the GitHub CLI (https://cli.github.com) if not already installed.

2. Verify the provenance for a release artifact:

```bash
gh attestation verify cascade_VERSION_linux_amd64.tar.gz \
--repo stablekernel/cascade \
--certificate-identity https://github.com/stablekernel/cascade/.github/workflows/release.yaml@refs/tags/vVERSION
```

This verifies that the artifact was built by the release workflow for the specified tag and that the provenance is signed by GitHub.

## Reproducing the build

Cascade builds are designed to be bit-for-bit reproducible using GoReleaser. To reproduce:

1. Check out the specific release tag:

```bash
git clone https://github.com/stablekernel/cascade.git
cd cascade
git checkout v0.X.Y
```

2. Ensure Go 1.25 is installed (the version used for official releases).

3. Build with the same flags used in the release workflow:

```bash
goreleaser build --single-target --clean --skip-post-hooks \
--id cascade
```

4. Compare the output with the official release binary:

```bash
sha256sum dist/cascade_linux_amd64/cascade
```

If the checksum matches the official checksums.txt, the build is reproducible.

## Trust model

cosign keyless signing uses an OIDC token issued by GitHub Actions during the release workflow. The token proves the signature was created during a specific GitHub Actions run in the cascade repository on the specified tag. Verification automatically confirms:

- The signature was created by the GitHub Actions runner (not a local machine).
- It was created during a release workflow run in the cascade repository.
- It is bound to the release workflow ref (the release tag) recorded in the certificate.

This model provides strong authenticity guarantees without requiring separate key distribution or management.
Loading