Skip to content

feat(oci): OCI registry ingestion + cosign verification for SBOM/VEX artifacts #192

Description

@jimmytoenners

Hi — I'd like to propose a new oci command family for sbom-tools. Before I open a PR I wanted to check whether you're open to the direction and the dependency footprint.

The gap

sbom-tools today is file-in: every command takes a local path (src/pipeline/parse.rs). To analyse the SBOM/VEX attached to a container image, an operator has to dance between cosign verify, cosign download sbom, cosign verify-attestation, then sbom-tools vex apply — each with its own flags and exit codes. Crucially, that manual flow never checks that the attestation's subject digest matches the image actually analysed ("right SBOM, wrong image"). I think that check belongs in the tool.

What it adds

Three subcommands, all behind an off-by-default oci feature:

sbom-tools oci pull   <REF> [OPTIONS]   # fetch + verify + write SBOM/VEX to disk
sbom-tools oci verify <REF> [OPTIONS]   # verify only; report + exit code (SARIF supported)
sbom-tools oci report <REF> [OPTIONS]   # pull + verify + enrich + vuln picture (one-shot)
  • Discovery: OCI 1.1 Referrers API + cosign tag-scheme fallback + plain artifacts
  • Cosign verification: key-based and keyless (Fulcio + Rekor inclusion + identity/issuer matching, per-attestation Fulcio chain validation)
  • Digest binding: in-toto subject digest is checked against the resolved image digest — the check the manual flow omits
  • New exit code 6 OCI_VERIFICATION_FAILED — next free after the existing 0–5
  • Pipeline unchanged: src/oci/ is an input resolver that materialises verified blobs as local files and hands them off; diff / view / quality / validate / vex / enrichment all "just work" on registry inputs

Full design doc (architecture, discovery matrix, SARIF rule catalog, test plan, risk register) on my fork:
https://github.com/jimmytoenners/sbom-tools/blob/feature/oci-registry-ingestion/docs/oci-verify-plan.md

Things you'll probably want to push back on

  1. New deps. sigstore = 0.13, oci-client = 0.15, tokio, plus base64 / x509-cert / pem / rustls-pki-types pinned directly (transitive via sigstore, promoted so the lifetime-erased ManualTrustRoot construction stays in our control). Sigstore is the only mature Rust path for cosign verification; pre-1.0, so versions are pinned with =x.y.z. All of this is gated behind --features ocicargo build with default features pulls in zero new deps.
  2. MSRV. Stays on Rust 1.88 — the pinned sigstore 0.13 + oci-client 0.15 compile clean on the current toolchain. No MSRV bump.
  3. async leakage. tokio is confined to src/oci/client.rs + verify.rs behind a block_on bridge. No async on any signature outside src/oci/. Same shape reqwest::blocking already uses internally under enrichment.
  4. CI surface. Adds two jobs: cargo build/test --features oci and cargo deny check --features oci. Default-feature jobs and the MSRV job are unchanged.

Status

Already implemented on a branch on my fork — 13 commits, your CI gates pass locally:

cargo fmt --check
cargo clippy --all-features -- -D warnings
cargo test --all-features

Branch: jimmytoenners:feature/oci-registry-ingestion

Each commit is a self-contained step (scaffold → registry fetch → DSSE/in-toto unwrap → key-based verify → keyless → per-attestation Fulcio chain → --trust-root + --insecure-ignore-tlog → SARIF emitter → docs). Happy to squash per your default; the per-commit history is just to make review easier.

How would you prefer this?

CONTRIBUTING.md says "keep PRs focused on a single change." I can do either:

  • (a) One PR — the whole branch as a single feature. Easier to land as a cohesive unit, but ~13 commits' worth of review surface.
  • (b) Split into 4 PRs, matching the phases in the design doc:
    1. Scaffold + registry fetch + --no-verify path
    2. Key-based cosign verification (image sig + DSSE envelopes + digest binding)
    3. Keyless (Fulcio + Rekor + identity + per-attestation chain) + --trust-root
    4. SARIF emitter + docs

I'll do whichever you prefer. Also entirely fine if the answer is "not a direction we want" or "slim it first" — better to find out before opening the PR than after.

Thanks!

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions