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
29 changes: 29 additions & 0 deletions .github/workflows/binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,35 @@ jobs:
lfsx-${{ matrix.target }}.tar.gz
lfsx-${{ matrix.target }}.zip

# The attestation above proves the same thing and proves it better, but it
# lives in GitHub's attestation store rather than beside the download, so
# nothing outside GitHub can see it and neither can a scanner reading the
# release. A signature bundle next to each archive is the portable form:
# verifiable with cosign alone, against this repository and this workflow.
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: Sign the archives
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag || github.event.release.tag_name }}
run: |
set -euo pipefail

target='${{ matrix.target }}'

# tar.gz on every platform but Windows, which gets the zip, so the
# pair this runner did not produce is skipped rather than guessed at.
for bin in lfsx-server lfsx ; do
for ext in tar.gz zip ; do
archive="${bin}-${target}.${ext}"
[ -f "$archive" ] || continue

cosign sign-blob --yes "$archive" --bundle "${archive}.sigstore"
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber "${archive}.sigstore"
done
done
Comment on lines +97 to +111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: if the archive names ever move (a taiki-e/upload-rust-binary-action bump, an archive: rename), every [ -f ] misses, the loop signs nothing and the step exits 0. The release then ships unsigned with a green workflow, which is the exact state this PR exists to fix. Two archives per runner is a fixed number, so assert it:

Suggested change
set -euo pipefail
target='${{ matrix.target }}'
# tar.gz on every platform but Windows, which gets the zip, so the
# pair this runner did not produce is skipped rather than guessed at.
for bin in lfsx-server lfsx ; do
for ext in tar.gz zip ; do
archive="${bin}-${target}.${ext}"
[ -f "$archive" ] || continue
cosign sign-blob --yes "$archive" --bundle "${archive}.sigstore"
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber "${archive}.sigstore"
done
done
set -euo pipefail
target='${{ matrix.target }}'
signed=0
# tar.gz on every platform but Windows, which gets the zip, so the
# pair this runner did not produce is skipped rather than guessed at.
for bin in lfsx-server lfsx ; do
for ext in tar.gz zip ; do
archive="${bin}-${target}.${ext}"
[ -f "$archive" ] || continue
cosign sign-blob --yes "$archive" --bundle "${archive}.sigstore"
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber "${archive}.sigstore"
signed=$((signed + 1))
done
done
# One archive per binary, whatever this runner's extension is. Zero
# or one means the names moved and the loop quietly matched nothing.
[ "$signed" -eq 2 ] || { echo "::error::signed $signed archives, expected 2"; exit 1; }


sbom:
name: SBOM
needs: build
Expand Down
20 changes: 17 additions & 3 deletions docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ and shipped with a CycloneDX SBOM.

## Verifying what you downloaded

Every release ships three kinds of proof: a `.sha256` beside each archive, a build provenance
attestation on each archive, and a CycloneDX SBOM per crate (`lfsx-server.cdx.json`,
`lfsx.cdx.json`). They answer different questions.
Every release ships four kinds of proof: a `.sha256` beside each archive, a build provenance
attestation on each archive, a `.sigstore` signature bundle beside each archive, and a CycloneDX
SBOM per crate (`lfsx-server.cdx.json`, `lfsx.cdx.json`). They answer different questions.
Comment on lines +23 to +25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "every release" is not true for the bundle, as the PR body says: v1.16.x and everything before it has no .sigstore asset, so a reader on a current download follows the cosign command below and finds nothing to pass to --bundle. Correct the version if the next tag is not v1.17.0.

Suggested change
Every release ships four kinds of proof: a `.sha256` beside each archive, a build provenance
attestation on each archive, a `.sigstore` signature bundle beside each archive, and a CycloneDX
SBOM per crate (`lfsx-server.cdx.json`, `lfsx.cdx.json`). They answer different questions.
Every release ships a `.sha256` beside each archive, a build provenance attestation on each
archive, and a CycloneDX SBOM per crate (`lfsx-server.cdx.json`, `lfsx.cdx.json`). Releases from
v1.17.0 on also ship a `.sigstore` signature bundle beside each archive. They answer different
questions.


The checksum proves the download survived the wire:

Expand All @@ -37,6 +37,20 @@ commit, which is the claim a checksum next to the artifact it checks cannot make
gh attestation verify lfsx-server-x86_64-unknown-linux-musl.tar.gz --repo FerrLabs/LFSX
```

The signature bundle says the same thing without asking GitHub. `gh attestation verify` reads
GitHub's attestation store, so it needs GitHub to answer; the bundle beside the archive is checked
against the public transparency log by cosign alone:

```bash
cosign verify-blob lfsx-server-x86_64-unknown-linux-musl.tar.gz \
--bundle lfsx-server-x86_64-unknown-linux-musl.tar.gz.sigstore \
--certificate-identity-regexp '^https://github.com/FerrLabs/LFSX/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
```

The two identity flags are the point of the check: without them cosign confirms that somebody
signed the bytes, not that this repository's workflow did.

The SBOM lists every crate in the build for scanners and licence tooling, and is attested the same
way. The container image is verified separately: it is signed with cosign at push and its
signature and SBOM live next to it in the registry.
Loading