Files
git.stella-ops.org/docs-archived/product/advisories/20260221 - Building a verifiable SBOM and attestation spine.md

8.5 KiB
Raw Permalink Blame History

Heres a compact, endtoend blueprint for making your SBOM evidence verifiable and auditable from build → attest → publish → verify → runtime, using only official specs/CLIs (CycloneDX, DSSE/intoto, cosign, Rekor v2), plus deterministic VEX and microwitness joins.


Why this matters (quick primer)

  • SBOM (software bill of materials) says whats inside an artifact.
  • DSSE / intoto wraps evidence in a tamperevident envelope.
  • cosign signs/verifies those envelopes.
  • Rekor (transparency log) proves when/what was published.
  • VEX states whether known CVEs affect your artifact.
  • Microwitnesses 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

  1. Emit CycloneDX v1.7 JSON.
  2. Canonicalize via JSONJCS → canonical_id := sha256(JCS(sbom.json)).

DSSE attestation (SBOM predicate)

  • Wrap CycloneDX content (or pointer) in an intoto 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_id target).

CI assertions (mustpass)

  • Unit: cyclonedx-cli validate ./bom.json && jcs_canonicalize ./bom.json | sha256sum → equals expected canonical_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.state and target match canonical_id.

LATER (scale/hardening)

  • Rekor v2 tile batching & multitile reconciliation.
  • Signed microwitness aggregation service.
  • Deterministic replay harness (nightly) for large volumes.
  • Predicate schema registry (stable predicateType URIs, semver).

Flow A: Buildtime SBOM + Attestation Anchor (endtoend)

  1. Producer:

    • CycloneDX v1.7 → validate: cyclonedx-cli validate ./bom.json.
    • JCS canonicalize → canonical_id = sha256(canonical_bytes).
  2. Predicate:

    • intoto Statement (predicateType = https://stella.example/predicate/sbom.v1 or SLSA provenance).
    • subject = [{"name":"artifact","digest":{"sha256":"<canonical_id>"}}].
    • predicate contains CycloneDX JCS content (or immutable pointer if too large).
  3. Sign & anchor:

    • cosign attest --predicate predicate.json --key cosign.key <subject-ref>.
    • Capture Rekor tile URL and entry_id; persist inside predicate metadata.
  4. 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 Microwitness Join

  1. VEX provider: Publish OpenVEX or CycloneDX VEX; DSSEsign; optionally anchor in Rekor.

  2. Ingest & map: OpenVEX → canonical CycloneDX VEX; store DSSE + rekor_tile + provenance to original feed.

  3. Runtime microwitness:

    • When observing a frame (pc → build_id → symbol), emit DSSE microwitness 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.

  4. Correlate: Join microwitness DSSE with VEX:

    • If canonical_id has not_affected for CVE X → autosuppress triage.
    • Else surface evidence and export both DSSEs + Rekor tiles in an audit pack.

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 / intoto 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>"}]}

Microwitness 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 (copypasteable)

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.state and target canonical_id are 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[]) → produce signed_score_dsse.
  • cosign verify-attestation and recompute score → byteexact 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 & hashcheck.
  • Missing symbol bundle: Emit unknown_state microwitness 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 microwitness DSSE → Join with VEX (not_affected ⇒ autosuppress), 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.