Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

.nornir/sbom-hosting-design.md

SBOM hosting — design

Class: design · Function: sbom (uses registry for the artifact coordinate, the shared nornir-service serve loop for the gRPC surface, and modgunn for the SBOM schema). The whole-system view is design.md; the parity gap this closes is feature-complete-gaps.md §6 (security / SBOM — an SBOM was generated in nornir but never hosted) and §11 (ops / misc — "SBOM hosting"), plus artifactory-parity-design.md §F. Sibling to the search verb, whose exact tri-surface shape it mirrors.

What this is

Until now an SBOM (Software Bill of Materials) was only generated — in nornir's security.rs, cargo-only — and never lived anywhere a consumer could pull it back. SBOM hosting makes holger store an SBOM attached to an artifact coordinate (repository, namespace/name/version) and serve it back byte-identical — an SBOM becomes a first-class servable artifact next to the package it describes.

It ships as server/lib/src/sbom.rs — a small on-disk store plus one serialization seam — behind the same three surfaces the search verb landed:

Surface Shape Auth
gRPC SbomService.AttachSbom attach a document to a coordinate write (writer/admin, RBAC fail-closed, scoped to the repo)
gRPC SbomService.FetchSbom fetch the document back open read (NOT_FOUND when unattached)
HTTP GET /-/sbom?repo=&namespace=&name=&version= fetch back (read twin of the RPC) open read (404 when unattached)
CLI holger-server sbom {attach,fetch} attach a file / fetch to a file in-process (drives the same store)

The chosen design

One coordinate-keyed content store, model consumed from modgunn.

Storage — a flat, coordinate-keyed file store

SbomStore roots at <data_root>/holger-sboms (a sibling of the promote PROD store + jera job ledger; data_root is the parent of the configured audit dir, so the gRPC endpoint, the HTTP gateway, and the CLI all derive one store from the config). Layout:

holger-sboms/<repo>/<coord>.sbom.json     — the hosted SBOM document bytes

<repo> and <coord> are reduced to path-safe single segments by promote's sanitize_repo / coord_key (now pub(crate) and reused, not twinned — the same helpers that guard the promote refs), so an untrusted coordinate straight off the wire can never escape the store root. coord is sanitize(namespace)__sanitize(name)__sanitize(version).

Model — consumed from modgunn (Law #5)

The SBOM data model is not re-declared here. modgunn is the single home of the nordisk SBOM schema; sbom.rs re-exports modgunn::sbom::{SbomInput, SbomComponent, SbomVuln, to_cyclonedx, purl} verbatim and adds exactly one thin seam:

pub fn build_cyclonedx(input: &SbomInput) -> Vec<u8>   // = serde_json::to_vec(modgunn::sbom::to_cyclonedx(input))

A producer holding a ready CycloneDX document attaches its raw bytes directly; a producer holding the structured model builds through build_cyclonedx here. Either way holger emits modgunn's CycloneDX 1.5 verbatim and can never fork the schema — a unit test asserts build_cyclonedx == modgunn::sbom::to_cyclonedx byte-for-byte (RED-when-broken if the model is ever twinned).

Why store raw bytes, not a re-parsed struct

The servable object is the document. Storing the exact attached bytes makes the round-trip byte-identical by construction (a producer signs/hashes the bytes it will get back), keeps the store schema-agnostic (a future CycloneDX/SPDX version needs no store change), and keeps holger a host, not a re-serializer.

Why attach is gRPC/CLI-only, fetch is also HTTP

Mirrors search + the rest of holger: writes go through the authenticated gRPC surface (and the in-process CLI); the HTTP door is a read-only convenience for HTTP-only consumers, reserved under /-/ so it never collides with a repository name. Same posture as GET /-/search.

Alternatives rejected

Observability

Every surface emits a gated #[cfg(feature="testmatrix")] functional_status cell (holger-grpc/attach_sbom, holger-grpc/fetch_sbom, holger-http/sbom, holger-cli/sbom) and audits attach as an Upload, fetch as a Download (including the 404). Tests: 6 store unit + 2 gRPC RPC + 2 HTTP door, each with a RED-when-broken not-found / wrong-coordinate guard.