Ophamin supply-chain provenance¶
Every artifact Ophamin publishes — the Python sdist + wheel, the Docker image at
ghcr.io/idirbenslama/ophamin, the Helm chart atoci://ghcr.io/idirbenslama/ophamin— carries cryptographic evidence of where it came from and is independently verifiable against the public Sigstore transparency log.
At a glance¶
| Artifact | Where | Signing | How to verify |
|---|---|---|---|
| Docker image | ghcr.io/idirbenslama/ophamin |
Sigstore keyless (cosign) | cosign verify ghcr.io/idirbenslama/ophamin@<digest> --certificate-identity-regexp=... |
| Docker image SBOM (CycloneDX) | attached to image as cosign attestation | Sigstore keyless (cosign) | cosign verify-attestation ... --type cyclonedx |
| Docker image SLSA provenance v1.0 | attached to image as GitHub-native attestation | Sigstore keyless (actions/attest-build-provenance@v2) |
gh attestation verify OR cosign verify-attestation ... --type slsaprovenance1 |
| Helm chart | oci://ghcr.io/idirbenslama/ophamin/ophamin |
Sigstore keyless (cosign) | cosign verify ghcr.io/idirbenslama/ophamin/ophamin@<digest> --certificate-identity-regexp=... |
EmpiricalProofRecord (proof JSON) |
per-deployment | HMAC-SHA256 (own key) | verify_proof_impl(json) / Rust + JS ports / HTTP /verify endpoint |
EmpiricalProofRecord (in-toto Statement) |
per-deployment | HMAC-SHA256 (own key, double-layer) | cosign verify-attestation — see INTEROP_OVERVIEW.md |
The three signing schemes are independent: cosign protects the distribution channel (Docker image + Helm chart), HMAC-SHA256 protects individual proof records, and the in-toto wrapper bridges the two (so an Ophamin proof can be signed by Sigstore when emitted into a SLSA-aware pipeline).
CI self-verifies every signature (0.46.0)¶
Every signing step in docker.yml + chart.yml is immediately
followed by a cosign verify step using the same certificate-
identity-regex consumers would use externally. A green CI run
means the signature is already known to verify with the
documented consumer commands below — no waiting for an external
consumer to surface signing-pipeline drift.
This closes the gap 0.42.0's CHANGELOG flagged. If the workflow file is renamed, the OIDC ref pattern changes, Fulcio is down, or the signature didn't actually land in Rekor, the self-verify step fails the workflow loudly in the same run as the publish.
Cosign keyless signing — how it works¶
Ophamin's docker.yml and chart.yml workflows sign every
published artifact via Sigstore's keyless flow:
-
GitHub Actions OIDC token — the workflow's
id-token: writepermission lets it request a short-lived OIDC token identifying the workflow's identity:https://github.com/IdirBenSlama/Ophamin/.github/workflows/docker.yml@refs/heads/main(or thev*tag ref, for tagged releases). -
Fulcio CA — Sigstore's Fulcio service receives the OIDC token, verifies it against GitHub's identity provider, and issues a short-lived X.509 certificate (10-minute lifetime) binding a fresh ephemeral key to that identity.
-
Signing —
cosign signuses the ephemeral key to sign the artifact's content digest. The signature, the ephemeral public key, and the Fulcio certificate are bundled as an OCI "signature artifact" and stored as a sibling of the original artifact in GHCR (e.g.ghcr.io/idirbenslama/ophamin:sha256-<digest>.sig). -
Rekor transparency log — cosign also records the signing event in Rekor, Sigstore's public transparency log. The signature's existence becomes tamper-evident and permanently auditable — anyone can fetch the log entry years later to confirm the artifact was signed at the time the publish workflow ran.
-
Key destruction — the ephemeral key is discarded immediately after signing. No private key material exists to be stolen or leaked.
Verifying an Ophamin Docker image¶
# Install cosign if you haven't (one-time)
brew install cosign # macOS
# or:
# go install github.com/sigstore/cosign/v2/cmd/cosign@latest
# Verify the :latest tag
cosign verify ghcr.io/idirbenslama/ophamin:latest \
--certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/docker\.yml@.*' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com
A successful verify prints the certificate's subject (workflow identity) + the Rekor log index + the bundle. If verification fails, cosign exits non-zero with a clear error message.
You can also pin verification to a specific commit-driven build:
# Verify a specific image by digest (not just by tag)
cosign verify ghcr.io/idirbenslama/ophamin@sha256:<digest> \
--certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/docker\.yml@.*' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com
Verifying the Docker image's SBOM (0.48.0)¶
Every published Docker image carries a CycloneDX SBOM attestation
signed via cosign keyless. The SBOM is an image-level scan
(Anchore syft) covering the base python:3.12-slim layer + pip-
installed packages — a complete dependency manifest the image
actually contains.
# Verify the SBOM attestation + extract it (one command)
cosign verify-attestation \
ghcr.io/idirbenslama/ophamin@sha256:<digest> \
--type cyclonedx \
--certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/docker\.yml@.*' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
| jq -r '.payload | @base64d | fromjson | .predicate'
The output is the raw CycloneDX JSON document the workflow
attested at publish time. The verify-attestation command:
- Confirms a signed attestation exists at the image digest
- Verifies the signature was produced by this workflow's OIDC identity (the cert-identity-regexp)
- Verifies inclusion in Rekor (the transparency log)
- Returns ALL matching attestations (multiple types coexist —
cyclonedxfor the SBOM,slsaprovenanceif a SLSA attestation is added later, etc.)
The attestation is an in-toto Statement v1 with the
predicateType set to the CycloneDX URL. Consumers can pipe
the predicate into any CycloneDX-aware tool (Dependency-Track,
GitHub dependency-graph upload, internal vulnerability
scanners).
Use in admission policies:
# policy-controller / ClusterImagePolicy that requires both a
# signature AND a signed SBOM attestation
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-ophamin-signed-with-sbom
spec:
images:
- glob: "ghcr.io/idirbenslama/ophamin*"
authorities:
- keyless:
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: ^https://github\.com/IdirBenSlama/Ophamin/.*
attestations:
- name: must-have-sbom
predicateType: https://cyclonedx.org/bom
Verifying the Docker image's SLSA L3 provenance (0.49.0)¶
In parallel to the SBOM attestation, every published Docker image carries a SLSA v1.0 build provenance attestation that documents how the image was built — the GitHub workflow URI, commit SHA, builder identity, invocation metadata, and the materials (source repo) the build consumed.
The attestation is produced by GitHub's native
actions/attest-build-provenance@v2 action (which uses the
same Sigstore keyless flow as cosign attest) and lands in both:
- GitHub's attestation registry — verifiable via
gh attestation verify - Sigstore Rekor + the image's OCI sibling slot — verifiable
via
cosign verify-attestation --type slsaprovenance1
Either tool path produces the same SLSA v1.0 in-toto Statement.
Via the gh CLI (simplest)¶
Pass --predicate-type https://slsa.dev/provenance/v1 to filter to
just the SLSA attestation (the same image may have multiple — SBOM
+ SLSA + cosign-signature).
Via cosign verify-attestation¶
cosign verify-attestation \
ghcr.io/idirbenslama/ophamin@sha256:<digest> \
--type slsaprovenance1 \
--certificate-identity-regexp='^https://github\.com/(IdirBenSlama/Ophamin|actions/attest-build-provenance)/.*' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
| jq -r '.payload | @base64d | fromjson | .predicate'
The output is the raw SLSA v1.0 predicate showing the build context. Example shape:
{
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"ref": "refs/heads/main",
"repository": "https://github.com/IdirBenSlama/Ophamin",
"path": ".github/workflows/docker.yml"
}
},
"resolvedDependencies": [
{"uri": "git+https://github.com/IdirBenSlama/Ophamin@refs/heads/main",
"digest": {"gitCommit": "<commit-sha>"}}
]
},
"runDetails": {
"builder": {"id": "https://github.com/actions/runner/github-hosted"},
"metadata": {"invocationId": "<workflow-run-url>"}
}
}
Three attestations, one image¶
After 0.49.0 every published Docker image carries three independent Sigstore-keyless attestations, all in Rekor:
- Image signature (0.42.0) — "this digest was published by our workflow"
- CycloneDX SBOM (0.48.0) — "this is what's inside"
- SLSA v1.0 provenance (0.49.0) — "this is how it was built"
Consumers can gate admission on any subset via
policy-controller + attestations: filters with the appropriate
predicateType URL.
Verifying an Ophamin Helm chart¶
# Verify the chart (replace <digest> with the actual digest from
# `helm show chart` or the workflow run summary)
cosign verify ghcr.io/idirbenslama/ophamin/ophamin@sha256:<digest> \
--certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/chart\.yml@.*' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com
Note the path differs: the Docker image is at
ghcr.io/idirbenslama/ophamin while the Helm chart is at
ghcr.io/idirbenslama/ophamin/ophamin (the trailing /ophamin
is the chart name segment within the owner's OCI namespace, and
helm push creates the subpath automatically based on
Chart.yaml's name: field).
Verification in Kubernetes admission¶
Use Sigstore policy-controller or Kyverno to require signed images cluster- wide:
# policy-controller / ClusterImagePolicy
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-ophamin-signed
spec:
images:
- glob: "ghcr.io/idirbenslama/ophamin*"
authorities:
- keyless:
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: ^https://github\.com/IdirBenSlama/Ophamin/.*
Pods pulling unsigned or wrongly-signed Ophamin images fail admission with a clear policy violation message.
Verifying an EmpiricalProofRecord¶
This is independent of cosign — each proof carries its own HMAC-SHA256 signature over the canonical body bytes (per SCHEMAS.md R1–R11):
from ophamin.measuring.proof.codec import load
record = load("path/to/proof.json")
assert record.verify_signature(b"the-deployment-key")
Cross-language verification:
// Rust port
use ophamin_proof::{parse_proof, verify_signature};
let record = parse_proof(&proof_text)?;
let ok = verify_signature(&record, b"the-deployment-key");
// JS/TS port
import { parseProof, verifySignature } from "@ophamin/proof";
const record = parseProof(proofText);
const ok = await verifySignature(record, key);
All three ports verify byte-identically against the same proof file — the cross-language conformance suite locks this property at every release.
Two-layer Sigstore + Ophamin verification¶
When an EmpiricalProofRecord flows through the in-toto wrapper
(to_in_toto_statement / to_dsse_envelope), the result is a
double-signed artifact: the outer DSSE envelope is signed by
whatever key the operator chooses (could be cosign keyless via
their own pipeline), and the inner Ophamin proof body is signed
by the original Ophamin HMAC key.
Verifying both layers:
import json, base64
from ophamin.interop import verify_dsse_envelope
from ophamin.measuring.proof.codec import load_from_dict
# 1. Verify outer DSSE
assert verify_dsse_envelope(envelope, dsse_key)
# 2. Decode inner Statement → predicate.body + predicate.signature
statement = json.loads(base64.b64decode(envelope["payload"]))
body = statement["predicate"]["body"]
sig = statement["predicate"]["signature"]
# 3. Verify inner Ophamin HMAC against the body
import hmac, hashlib
from ophamin.measuring.proof.record import _canonical
expected = hmac.new(
ophamin_key,
_canonical(body).encode("utf-8"),
hashlib.sha256,
).hexdigest()
assert hmac.compare_digest(expected, sig)
See INTEROP_OVERVIEW.md §"I want my proof on Sigstore / Rekor / SLSA infrastructure" for the full flow.
Trust model summary¶
What each signature guarantees:
-
Cosign signature on Docker image / Helm chart — the artifact was published by Ophamin's GitHub Actions workflow at a specific commit. The signature is publicly verifiable; the Rekor entry is publicly auditable. The user does NOT need to trust GHCR's storage layer — tampering with the artifact bytes in-place would break the signature.
-
HMAC signature on a proof record — the proof body was produced by a process holding the deployment's HMAC key. The user trusts the key holder (typically the Ophamin operator). Different operators use different keys; cross-operator trust is established by sharing keys or by layering Sigstore on top via the in-toto wrapper.
-
In-toto / DSSE wrapper — bridges the two by carrying the proof body as a DSSE payload that can be signed via cosign keyless OR by HMAC. Two-layer verification gives both cryptographic provenance (cosign) AND content authenticity (Ophamin HMAC).
What this does NOT include¶
- Reproducible-build attestations (SLSA Level 3+) — the Docker image build is not yet a fully reproducible Bazel / Nix build. The published image is signed but two builds at the same commit may differ at the byte level (timestamps, debian package ordering). Closing this requires a deeper rebuild of the Dockerfile around a reproducible-build framework.
- SBOM signing — the SBOM (CycloneDX, per the
interop/cyclonedx.pyexporter) is itself an Ophamin proof record that uses HMAC. A future ship can also sign the SBOM via cosign for cross-format provenance. - Cosign signing for the Python package —
ophaminon PyPI is not yet signed via PyPI's trusted publishing + attestations. Therelease.ymlworkflow is the operator-physical owner step for this; the chart-publish workflow's pattern is reusable.
See also¶
- Sigstore Cosign documentation
- SLSA framework
- in-toto Attestation v1 spec
- INTEROP_OVERVIEW.md — full Ophamin interop catalogue
- SCHEMAS.md — proof record canonical form
.github/workflows/docker.yml— image publishing + signing.github/workflows/chart.yml— chart publishing + signing