feat(release): generate reproducible CycloneDX SBOM

This commit is contained in:
2026-07-21 12:24:49 +02:00
parent 397c87a012
commit c34892f6ea
3 changed files with 473 additions and 0 deletions

126
tests/test_release_sbom.py Normal file
View File

@@ -0,0 +1,126 @@
from __future__ import annotations
import base64
from datetime import UTC, datetime
import hashlib
from pathlib import Path
import sys
import unittest
META_ROOT = Path(__file__).resolve().parents[1]
RELEASE_TOOLS_ROOT = META_ROOT / "tools" / "release"
if str(RELEASE_TOOLS_ROOT) not in sys.path:
sys.path.insert(0, str(RELEASE_TOOLS_ROOT))
from govoplan_release.sbom import build_release_sbom, render_sbom, sbom_sha256, validate_sbom # noqa: E402
class ReleaseSbomTests(unittest.TestCase):
@staticmethod
def _build_at(timestamp: datetime) -> dict:
return build_release_sbom(
product_version="1.2.3",
pip_inspect={
"version": "1",
"installed": [{"metadata": {"name": "FastAPI", "version": "0.100.0"}}],
},
npm_lock={"packages": {"": {"name": "govoplan", "version": "1.2.3"}}},
timestamp=timestamp,
)
def test_builds_valid_deduplicated_python_and_npm_components(self) -> None:
integrity = base64.b64encode(b"example digest").decode("ascii")
payload = build_release_sbom(
product_version="1.2.3",
pip_inspect={
"version": "1",
"installed": [
{"metadata": {"name": "FastAPI", "version": "0.100.0"}},
{"metadata": {"name": "FastAPI", "version": "0.100.0"}},
],
},
npm_lock={
"packages": {
"": {"name": "govoplan", "version": "1.2.3"},
"node_modules/@scope/example": {
"name": "@scope/example",
"version": "4.5.6",
"integrity": f"sha512-{integrity}",
},
}
},
)
validate_sbom(payload)
self.assertEqual(2, len(payload["components"]))
npm = next(item for item in payload["components"] if item["name"] == "@scope/example")
self.assertEqual("SHA-512", npm["hashes"][0]["alg"])
self.assertIn("pkg:npm/%40scope/example@4.5.6", npm["purl"])
self.assertEqual(
hashlib.sha256(render_sbom(payload).encode("utf-8")).hexdigest(),
sbom_sha256(payload),
)
def test_rejects_empty_component_inventory(self) -> None:
payload = build_release_sbom(
product_version="1.2.3",
pip_inspect={"version": "1", "installed": []},
npm_lock={"packages": {"": {"version": "1.2.3"}}},
)
with self.assertRaisesRegex(ValueError, "no dependency components"):
validate_sbom(payload)
def test_fixed_timestamp_produces_identical_bytes_and_digest(self) -> None:
timestamp = datetime(2026, 7, 21, 8, 30, tzinfo=UTC)
first = self._build_at(timestamp)
second = self._build_at(timestamp)
self.assertEqual(render_sbom(first), render_sbom(second))
self.assertEqual(sbom_sha256(first), sbom_sha256(second))
def test_timestamp_changes_serial_and_digest(self) -> None:
first = self._build_at(datetime(2026, 7, 21, 8, 30, tzinfo=UTC))
second = self._build_at(datetime(2026, 7, 21, 8, 31, tzinfo=UTC))
self.assertNotEqual(first["serialNumber"], second["serialNumber"])
self.assertNotEqual(sbom_sha256(first), sbom_sha256(second))
def test_local_and_credential_bearing_distribution_urls_are_omitted(self) -> None:
payload = build_release_sbom(
product_version="1.2.3",
pip_inspect={
"version": "1",
"installed": [
{
"metadata": {"name": "LocalPackage", "version": "1.0.0"},
"direct_url": {"url": "file:///private/build/govoplan-core"},
},
{
"metadata": {"name": "PrivatePackage", "version": "1.0.0"},
"direct_url": {"url": "https://user:secret@example.test/private.whl"},
},
{
"metadata": {"name": "PublicPackage", "version": "1.0.0"},
"direct_url": {"url": "https://example.test/public.whl"},
},
],
},
npm_lock={"packages": {"": {"name": "govoplan", "version": "1.2.3"}}},
timestamp=datetime(2026, 7, 21, 8, 30, tzinfo=UTC),
)
by_name = {component["name"]: component for component in payload["components"]}
self.assertNotIn("externalReferences", by_name["LocalPackage"])
self.assertNotIn("externalReferences", by_name["PrivatePackage"])
self.assertEqual(
[{"type": "distribution", "url": "https://example.test/public.whl"}],
by_name["PublicPackage"]["externalReferences"],
)
if __name__ == "__main__":
unittest.main()