Skip to content

Latest commit

 

History

History
191 lines (149 loc) · 6.87 KB

VERIFICATION.md

File metadata and controls

191 lines (149 loc) · 6.87 KB

Verifying a LAIN release

This document explains how to verify a lain release artifact on a fresh machine, with no prior context. Each step links to the artifact that proves the previous one. See docs/SUPPLY_CHAIN.md for why these checks exist.

What ships in a release

The updated release workflow publishes the files below for new tags. Historical releases predating v0.7.4 may lack checksums, SBOMs, or provenance; these files are not retroactively generated.

File What it is
lain-<ver>-<target>.tar.gz The compressed lain binary
lain-<ver>-<target>.tar.gz.sha256 <hash> <filename> for that tarball
lain-<ver>-<target>.tar.gz.cdx.json CycloneDX SBOM of that tarball
lain-<ver>-<target>.tar.gz.sigstore.json Full signed attestation bundle (build provenance)
lain-<ver>-<target>.tar.gz.intoto.jsonl Signed in-toto envelope extracted from that bundle
lain-<ver>-<target>.tar.gz.cosign.bundle.json Direct cosign keyless signature over the tarball's bytes
SHA256SUMS <hash> <filename> for all three tarballs
server.json MCP registry manifest

Plus, for each tarball, a build-provenance attestation signed using GitHub Actions' OIDC identity during the build and discoverable via gh attestation verify. The attestation is generated by actions/attest-build-provenance, which produces provenance meeting SLSA v1.0 Build Level 2 — the claim the README's "Build Provenance" badge links here to substantiate.

Separately, each tarball also carries a direct cosign keyless signature (.cosign.bundle.json), produced by sigstore/cosign-installer

  • cosign sign-blob using the same GitHub Actions OIDC identity via Fulcio/Rekor. This is independent of the provenance attestation above: it's a direct statement "this exact blob was signed by this identity," verifiable with only the cosign CLI — no gh / GitHub API call required, useful for verifying outside a GitHub-aware environment.

The supported <target> triples today are:

  • x86_64-unknown-linux-gnu
  • aarch64-apple-darwin
  • x86_64-pc-windows-msvc

Verification flow

The example below uses Linux x86_64. Set VER to a release that includes the files listed above; releases before v0.7.4 do not include them (some, lacking these artifacts entirely, have been removed -- see CHANGELOG.md).

1. Download the artifact and its checksums

VER='REPLACE_WITH_RELEASE_VERSION'
TARGET=x86_64-unknown-linux-gnu

mkdir lain-verify && cd lain-verify

# The tarball itself
gh release download "v${VER}" \
  --repo spuentesp/lain \
  --pattern "lain-${VER}-${TARGET}.tar.gz"

# Per-binary checksum
gh release download "v${VER}" \
  --repo spuentesp/lain \
  --pattern "lain-${VER}-${TARGET}.tar.gz.sha256"

# Aggregate checksum
gh release download "v${VER}" \
  --repo spuentesp/lain \
  --pattern SHA256SUMS

2. Verify the SHA256

Either check the per-binary sidecar:

sha256sum -c "lain-${VER}-${TARGET}.tar.gz.sha256"
# expected output:
# lain-<version>-x86_64-unknown-linux-gnu.tar.gz: OK

Or verify the same hash appears in the aggregate file:

grep "lain-${VER}-${TARGET}.tar.gz" SHA256SUMS \
  | sha256sum -c

3. Verify build provenance

gh attestation verify \
  "lain-${VER}-${TARGET}.tar.gz" \
  --repo spuentesp/lain
# expected output ends with:
# ✓ Verification succeeded!

You can also verify using the downloaded bundle:

gh release download "v${VER}" --repo spuentesp/lain \
  --pattern "lain-${VER}-${TARGET}.tar.gz.sigstore.json"
gh attestation verify "lain-${VER}-${TARGET}.tar.gz" \
  --repo spuentesp/lain \
  --bundle "lain-${VER}-${TARGET}.tar.gz.sigstore.json"

The .intoto.jsonl file preserves the signed DSSE envelope for tools that consume in-toto provenance. Use the full .sigstore.json bundle for the verification command above; it also contains verification material.

The attestation is signed using GitHub Actions' OIDC identity during the build, binding the artifact to the specific commit and workflow run that produced it. If the tarball was modified in transit, or if a fork's release workflow claimed to produce it, this command fails.

3b. Verify the cosign signature (alternative, no gh required)

gh release download "v${VER}" --repo spuentesp/lain \
  --pattern "lain-${VER}-${TARGET}.tar.gz.cosign.bundle.json"

cosign verify-blob \
  --bundle "lain-${VER}-${TARGET}.tar.gz.cosign.bundle.json" \
  --certificate-identity-regexp "^https://github.com/spuentesp/lain/\.github/workflows/release\.yml@refs/tags/v.*$" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  "lain-${VER}-${TARGET}.tar.gz"
# expected output ends with:
# Verified OK

This checks the same underlying claim as step 3 (the tarball was built and signed by this repo's release.yml, via GitHub's OIDC identity), but only needs the cosign CLI — useful in environments without gh or GitHub API access.

4. Inspect the SBOM (optional)

gh release download "v${VER}" \
  --repo spuentesp/lain \
  --pattern "lain-${VER}-${TARGET}.tar.gz.cdx.json"

# Human-readable component summary
python -c "
import json, sys
sbom = json.load(open('lain-${VER}-${TARGET}.tar.gz.cdx.json'))
for comp in sbom.get('components', []):
    print(f\"{comp.get('type', '?'):>10}  {comp.get('name', '?')}@{comp.get('version', '?')}\")
" | sort

Or upload to a CycloneDX-aware tool (Dependency-Track, Grype, etc.) for deeper analysis.

5. Extract and run

tar xzf "lain-${VER}-${TARGET}.tar.gz"
./lain --version
# expected output: lain <version>

What to do if a check fails

Failure Likely cause Action
sha256sum -c says FAILED Downloaded file corrupted, or wrong artifact Re-download with gh release download
gh attestation verify fails Wrong artifact, or workflow changed Cross-check the artifact name matches the release tag
SBOM parser errors File truncated Re-download; report if reproducible
lain --version mismatch Wrong binary for your platform Re-check the <target> component

If the SHA256 and provenance checks both pass but lain --version mismatches the tag, stop and report — that's an artifact-vs-build disagreement that should not be possible if the workflow ran cleanly.

Continuous verification

docs/SUPPLY_CHAIN.md explains why these checks exist and what they protect against. scripts/check-release-version.py is the in-repo gate that ensures the metadata side of every release is consistent across Cargo.toml, Cargo.lock, server.json, npm-shim/package.json, and Formula/lain.rb. The scripts/test_sync_metadata.py smoke test (added in PR #2) catches the description / homepage / keywords drift that the version check ignores.