8.5 KiB
Here’s a compact, end‑to‑end blueprint for making your SBOM evidence verifiable and auditable from build → attest → publish → verify → runtime, using only official specs/CLIs (CycloneDX, DSSE/in‑toto, cosign, Rekor v2), plus deterministic VEX and micro‑witness joins.
Why this matters (quick primer)
- SBOM (software bill of materials) says what’s inside an artifact.
- DSSE / in‑toto wraps evidence in a tamper‑evident envelope.
- cosign signs/verifies those envelopes.
- Rekor (transparency log) proves when/what was published.
- VEX states whether known CVEs affect your artifact.
- Micro‑witnesses are runtime breadcrumbs you can join back to SBOM/VEX for triage.
NOW (MVP you can ship immediately)
Goal: Minimal, reproducible spine with canonical SBOMs, signed attestations, Rekor v2 anchoring, and deterministic VEX ingestion.
Canonical SBOM & ID
- Emit CycloneDX v1.7 JSON.
- Canonicalize via JSON‑JCS →
canonical_id := sha256(JCS(sbom.json)).
DSSE attestation (SBOM predicate)
- Wrap CycloneDX content (or pointer) in an in‑toto Statement.
subject[0].digest.sha256 == canonical_id.- Sign with cosign (DSSE mode).
- Capture Rekor v2 tile pointer / entry id and embed it in predicate metadata.
Rekor v2 anchoring
- cosign publish creates the log entry.
- Store the tile URL and entry id in your artifact record.
Deterministic VEX ingestion (OpenVEX & CycloneDX VEX)
- Ingest OpenVEX or CycloneDX VEX, map to a canonical CycloneDX VEX form.
- Apply strict merge rules (source priority, timestamp, exact
canonical_idtarget).
CI assertions (must‑pass)
- Unit:
cyclonedx-cli validate ./bom.json && jcs_canonicalize ./bom.json | sha256sum→ equals expectedcanonical_id. - Integration:
cosign attest --predicate predicate.json --key cosign.key <subject-ref>cosign verify-attestation --key <pubkey> --type in-toto <subject-ref>Rekor proof:rekor-cli tile get --entry <entry_id>(or v2 tiles client) → inclusion proof valid. - VEX mapping: jsonschema validate; assert
vulnerabilities[].analysis.stateand target matchcanonical_id.
LATER (scale/hardening)
- Rekor v2 tile batching & multi‑tile reconciliation.
- Signed micro‑witness aggregation service.
- Deterministic replay harness (nightly) for large volumes.
- Predicate schema registry (stable
predicateTypeURIs, semver).
Flow A: Build‑time SBOM + Attestation Anchor (end‑to‑end)
-
Producer:
- CycloneDX v1.7 → validate:
cyclonedx-cli validate ./bom.json. - JCS canonicalize →
canonical_id = sha256(canonical_bytes).
- CycloneDX v1.7 → validate:
-
Predicate:
- in‑toto Statement (
predicateType = https://stella.example/predicate/sbom.v1or SLSA provenance). subject = [{"name":"artifact","digest":{"sha256":"<canonical_id>"}}].predicatecontains CycloneDX JCS content (or immutable pointer if too large).
- in‑toto Statement (
-
Sign & anchor:
cosign attest --predicate predicate.json --key cosign.key <subject-ref>.- Capture Rekor tile URL and entry_id; persist inside predicate metadata.
-
Verify:
- Fetch referrers (OCI Referrers 1.1), extract DSSE.
cosign verify-attestation --key <pubkey> --type in-toto <subject-ref>.- Recompute
sha256(JCS(sbom.json))==subject.digest.sha256. - Validate Rekor v2 inclusion proof (tile check).
Flow B: VEX → Apply → Runtime Micro‑witness Join
-
VEX provider: Publish OpenVEX or CycloneDX VEX; DSSE‑sign; optionally anchor in Rekor.
-
Ingest & map: OpenVEX → canonical CycloneDX VEX; store DSSE +
rekor_tile+ provenance to original feed. -
Runtime micro‑witness:
-
When observing a frame (
pc → build_id → symbol), emit DSSE micro‑witness predicate referencing:subject.canonical_id- symbol bundle (OCI referrer,
mediaType=application/vnd.stella.symbols+tar) replay_token(points to deterministic replay inputs)
-
Sign & anchor to Rekor v2.
-
-
Correlate: Join micro‑witness DSSE with VEX:
- If
canonical_idhasnot_affectedfor CVE X → auto‑suppress triage. - Else surface evidence and export both DSSEs + Rekor tiles in an audit pack.
- If
Minimal, unambiguous schemas
1) Artifact canonical record
{"canonical_id":"sha256:<hex>","format":"cyclonedx-jcs:1",
"sbom_ref":"oci://registry/repo@sha256:<digest>|object-store://bucket/path",
"attestations":[{"type":"in-toto","dsse_b64":"<base64>","rekor_tile":"<url>","entry_id":"<id>"}],
"referrers":[{"mediaType":"application/vnd.stella.symbols+tar","descriptor_digest":"sha256:<digest>","registry":"ghcr.io/org/repo"}],
"vex_refs":[{"type":"openvex","dsse_b64":"<base64>","rekor_tile":"<url>"}]}
2) Minimal DSSE / in‑toto predicate (for SBOM)
{"_type":"https://in-toto.io/Statement/v0.1",
"subject":[{"name":"artifact","digest":{"sha256":"<canonical_id>"}}],
"predicateType":"https://stella.example/predicate/sbom.v1",
"predicate":{"bom_format":"cyclonedx-jcs:1","bom":"<base64-or-pointer>",
"producer":{"name":"ci-service","kid":"<signer-kid>"},
"timestamp":"2026-02-19T12:34:56Z",
"rekor_tile":"https://rekor.example/tiles/<tile>#<entry>"}}
3) OpenVEX → CycloneDX VEX mapping (sample OpenVEX)
{"vexVersion":"1.0.0","id":"vex-123",
"statements":[{"vulnerability":"CVE-2025-0001",
"product":"pkg:maven/org/example@1.2.3",
"status":"not_affected","justification":"code_not_present",
"timestamp":"2026-02-19T12:00:00Z","provider":{"name":"vendor"}}]}
CycloneDX SBOM (trimmed example)
{"bomFormat":"CycloneDX","specVersion":"1.7",
"components":[{"bom-ref":"pkg:maven/org/example@1.2.3",
"type":"library","name":"example","version":"1.2.3",
"purl":"pkg:maven/org/example@1.2.3"}]}
DSSE envelope (shape)
{"payloadType":"application/vnd.in-toto+json",
"payload":"<base64-of-predicate.json>",
"signatures":[{"keyid":"cosign:ecdsa-1","sig":"<base64>"}]}
Micro‑witness predicate (sample)
{"predicateType":"https://stella.example/micro-witness.v1",
"subject":[{"name":"artifact","digest":{"sha256":"<canonical_id>"}}],
"predicate":{"trace_id":"trace-abc",
"frames":[{"pc":"0x400123","build_id":"<buildid>","symbol":"main","offset":123}],
"symbol_refs":[{"type":"oci-ref","ref":"ghcr.io/org/repo@sha256:<digest>",
"mediaType":"application/vnd.stella.symbols+tar"}],
"replay_token":"s3://bucket/replays/trace-abc.json",
"timestamp":"2026-02-19T12:40:00Z"}}
CI: authoritative checks (copy‑pasteable)
Unit (canonicalization):
cyclonedx-cli validate ./bom.json
jcs_canonicalize ./bom.json | sha256sum # == canonical_id
Integration (sign + verify + Rekor):
cosign attest --predicate predicate.json --key cosign.key <subject-ref>
cosign verify-attestation --key <pubkey> --type in-toto <subject-ref>
rekor-cli tile get --entry <entry_id> # validate inclusion proof
VEX mapping:
- Convert OpenVEX → CycloneDX VEX; jsonschema validate.
- Assert:
vulnerabilities[].analysis.stateand targetcanonical_idare exact.
E2E (nightly, reproducibility POC):
stella-replay --token <replay_token> --seed <seed> # same signed_score DSSE & same Rekor tile id
Deterministic signed score (gate):
- Given
(seed, canonical_id, evidence_ids[])→ producesigned_score_dsse. cosign verify-attestationand recompute score → byte‑exact equality.
Failure modes (and what to do)
- Payload > Rekor limit: Put artifact in immutable object store; Rekor entry contains digest + pointer; embed pointer in predicate; verifier must fetch & hash‑check.
- Missing symbol bundle: Emit
unknown_statemicro‑witness entry; surface unknowns explicitly in reports. - Digest mismatch anywhere: fail the build—no exceptions.
Two concrete sample flows (ready to demo in Stella Ops)
- Flow A: Canonical CycloneDX → DSSE sign (cosign) → Rekor v2 tile captured → Verify via referrers + DSSE + tile proof.
- Flow B: Ingest OpenVEX → Map → Apply to
canonical_id→ Runtime micro‑witness DSSE → Join with VEX (not_affected⇒ auto‑suppress), export audit pack containing both DSSEs + Rekor tiles.
If you want, I can turn this into:
- a CI job pack (GitLab/YAML) with all commands and assertions,
- minimal JSON schemas for the predicates,
- a sample repo with fixtures (SBOM, predicates, signed envelopes) you can run locally with cosign/Rekor.