Skip to content

Verifying releases

Every SpeculaPro release is built, signed and published by an automated release process. Before you run a release, you can prove two things:

  • It came from SpeculaPro’s release process, not from someone who got hold of a registry password: the image carries a signature, which you check with cosign.
  • What is inside it: each release has a software bill of materials (SBOM) listing every component and version, so your security team can check it against vulnerability advisories.

Then deploy the image by its digest (sha256:…), so the cluster runs exactly the image you checked, whatever its tag points to later.

A release is a Git tag such as v1.2.3. For each one, the release process publishes:

Item Where
The container image for linux/amd64 and linux/arm64 ghcr.io/spalce/speculapro, tagged 1.2.3 and 1.2
A signature on the image Stored with the image in the registry, and recorded in the public Sigstore transparency log (Rekor)
The SBOM, in SPDX JSON Attached to the image as a signed attestation, and as speculapro-v1.2.3.spdx.json on the GitHub release
Build provenance (how and from which commit it was built) Attached to the image
The Helm chart speculapro-1.2.3.tgz on the GitHub release
An offline bundle of the image with its signatures speculapro-v1.2.3-oci.tar.gz on the GitHub release
The image digest image-digest.txt on the GitHub release, and in the release notes

The signature is keyless: there is no long-lived SpeculaPro signing key that could leak. When the release process runs, the build service vouches for its identity (this is the release.yml workflow of the spalce/speculapro repository, running for tag v1.2.3), a short-lived certificate is issued for that identity, and the image is signed with it. The signature and certificate are recorded in the public transparency log.

Checking the signature therefore means checking who signed: the identity must be SpeculaPro’s release workflow, running for a version tag.

You need cosign 2 or later on the machine you check from.

  1. Find the digest of the release, from image-digest.txt on the GitHub release or the release notes.

  2. Verify the signature:

    Terminal window
    cosign verify ghcr.io/spalce/speculapro@sha256:<digest> \
    --certificate-identity-regexp '^https://github.com/spalce/speculapro/.github/workflows/release.yml@refs/tags/v' \
    --certificate-oidc-issuer https://token.actions.githubusercontent.com

    It prints the verified signature details and exits with status 0. Anything else, such as no matching signatures, means don’t deploy it: tell [email protected].

  3. Verify the SBOM attestation, with the same identity:

    Terminal window
    cosign verify-attestation ghcr.io/spalce/speculapro@sha256:<digest> --type spdxjson \
    --certificate-identity-regexp '^https://github.com/spalce/speculapro/.github/workflows/release.yml@refs/tags/v' \
    --certificate-oidc-issuer https://token.actions.githubusercontent.com
  4. Deploy by digest. In the Helm chart set image.digest: sha256:<digest>; with Docker Compose set SPECULAPRO_IMAGE=ghcr.io/spalce/speculapro@sha256:<digest> in .env.

The SBOM lists every package in the image: the Go modules compiled into the program, and the few files of the distroless base image. Load speculapro-v1.2.3.spdx.json into your vulnerability scanner or software inventory. Because it is also a signed attestation on the image, you can be sure the list belongs to the image you verified.

How to bring a release into an air-gapped network

Section titled “How to bring a release into an air-gapped network”

On a connected machine:

  1. Verify the image as above.

  2. Save the image with all its architectures, signatures and the SBOM attestation, plus the images it depends on if you use Docker Compose:

    Terminal window
    cosign save ghcr.io/spalce/speculapro@sha256:<digest> --dir speculapro-oci
    skopeo copy --all docker://postgres:18 oci-archive:postgres-18.tar
    skopeo copy --all docker://docker.redpanda.com/redpandadata/redpanda:v24.3.1 oci-archive:redpanda.tar
    skopeo copy --all docker://valkey/valkey:8.1 oci-archive:valkey.tar

    The GitHub release’s speculapro-v1.2.3-oci.tar.gz is the same cosign save output, ready made. The Helm chart’s migration Job also uses postgres:18, for its psql step.

  3. Run cosign initialize, so the Sigstore trust root is saved under ~/.sigstore/.

  4. Carry the files and the ~/.sigstore/ directory across with your approved media.

Inside the network:

Terminal window
cosign load --dir speculapro-oci registry.internal/speculapro/speculapro
skopeo copy oci-archive:postgres-18.tar docker://registry.internal/library/postgres:18

Then verify again, from inside, with the same identity flags plus --offline. Offline verification uses the transparency-log proof stored with the signature and the trust root you copied, so it needs no connection:

Terminal window
cosign verify --offline registry.internal/speculapro/speculapro@sha256:<digest> \
--certificate-identity-regexp '^https://github.com/spalce/speculapro/.github/workflows/release.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

Point the Helm chart’s image.repository (and migrations.grants.image), or Compose’s SPECULAPRO_IMAGE, at your internal registry, by digest.

Licences and rule-pack updates work offline too. See Licensing and Signed rule-pack updates.