Skip to content

Commit 01e9dd7

Browse files
Add sbom generation tooling (#2232)
1 parent 092d229 commit 01e9dd7

25 files changed

Lines changed: 3286 additions & 0 deletions

sbom/BUILD.bazel

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SBOM Generation Package
2+
#
3+
# This package provides Bazel-native SBOM (Software Bill of Materials) generation
4+
# using module extensions and aspects.
5+
#
6+
# Public API:
7+
# - load("@score_tooling//sbom:defs.bzl", "sbom")
8+
# - use_extension("@score_tooling//sbom:extensions.bzl", "sbom_metadata")
9+
10+
load("@rules_python//python:defs.bzl", "py_library")
11+
12+
package(default_visibility = ["//visibility:public"])
13+
14+
exports_files([
15+
"defs.bzl",
16+
"extensions.bzl",
17+
"repos.bzl",
18+
"repository_rules.bzl",
19+
])
20+
21+
# Filegroup for all SBOM-related bzl files
22+
filegroup(
23+
name = "bzl_files",
24+
srcs = [
25+
"defs.bzl",
26+
"extensions.bzl",
27+
"//sbom/internal:bzl_files",
28+
],
29+
)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Detailed SBOM Implementation Approach for Eclipse SCORE
2+
3+
## Executive Summary
4+
5+
This proposal addresses the existing backlog items ([#2144](https://github.com/eclipse-score/score/issues/2144), [#2232](https://github.com/eclipse-score/score/issues/2232), [#2060](https://github.com/eclipse-score/score/issues/2060), [#2103](https://github.com/eclipse-score/score/issues/2103)) and provides a comprehensive implementation roadmap for SBOM generation in Eclipse SCORE.
6+
7+
---
8+
9+
## High-Level Architecture
10+
11+
12+
```
13+
┌─────────────────────────────────────────────────────────────────────────────┐
14+
│ SCORE SBOM ARCHITECTURE │
15+
├─────────────────────────────────────────────────────────────────────────────┤
16+
│ │
17+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
18+
│ │ Rust │ │ C++ │ │ Bazel │ │
19+
│ │ Cargo.toml │ │ http_archive│ │ MODULE.bazel│ │
20+
│ │ (metadata) │ │ git_override│ │ (bazel_dep) │ │
21+
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
22+
│ │ │ │ │
23+
│ ▼ ▼ ▼ │
24+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
25+
│ │ SBOM GENERATOR MODULE │ │
26+
│ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │
27+
│ │ │ Bazel Aspect │ │ Metadata Extension │ │ │
28+
│ │ │ (dep graph traversal│ │ (license/supplier │ │ │
29+
│ │ │ via sbom_aspect) │ │ from MODULE.bazel)│ │ │
30+
│ │ └──────────────────────┘ └──────────────────────┘ │ │
31+
│ └─────────────────────────────────────────────────────────────────────┘ │
32+
│ │ │
33+
│ ┌─────────────────┴─────────────────┐ │
34+
│ ▼ ▼ │
35+
│ ┌─────────────┐ ┌─────────────┐ │
36+
│ │ SPDX 2.3 │ │ CycloneDX │ │
37+
│ │ .spdx.json │ │ 1.6 .json │ │
38+
│ └─────────────┘ └─────────────┘ │
39+
│ │
40+
└─────────────────────────────────────────────────────────────────────────────┘
41+
```
42+
43+
### 2.2 Integration with Existing SCORE Tooling
44+
45+
Dash is a **license compliance checker** only (no SBOM output, no VEX).
46+
SBOM generation is a new, separate module that complements Dash.
47+
48+
```
49+
┌─────────────────────────────────────────────────────────────────────────────┐
50+
│ eclipse-score/tooling │
51+
├─────────────────────────────────────────────────────────────────────────────┤
52+
│ │
53+
│ EXISTING NEW (IMPLEMENTED) │
54+
│ ──────── ────────────────── │
55+
│ ├── dash/ ├── sbom/ │
56+
│ │ └── dash_license_checker │ ├── defs.bzl │
57+
│ │ (license compliance) │ │ └── sbom() macro │
58+
│ ├── cr_checker/ │ ├── extensions.bzl │
59+
│ │ └── copyright_checker │ │ └── sbom.license() │
60+
│ │ (header validation) │ ├── internal/ │
61+
│ │ │ │ ├── aspect.bzl (dep traversal) │
62+
│ │ │ │ ├── rules.bzl (build rule) │
63+
│ │ │ │ └── generator/ │
64+
│ │ │ │ ├── sbom_generator.py │
65+
│ │ │ │ ├── spdx_formatter.py │
66+
│ │ │ │ ├── cyclonedx_formatter.py │
67+
│ │ │ │ └── purl.py │
68+
│ │ │ └── tests/ │
69+
│ │ │ │
70+
│ COMPLEMENTARY WORKFLOW │ │
71+
│ ────────────────────── │ │
72+
│ Dash: checks if dependency │ │
73+
│ licenses are allowed by policy │ │
74+
│ SBOM: generates .spdx.json / │ │
75+
│ .cdx.json listing all deps │ │
76+
│ with name, version, license, │ │
77+
│ supplier, PURL │ │
78+
│ │ │
79+
│ VALIDATION (external, optional) │ │
80+
│ ──────────────────────────── │ │
81+
│ pip install spdx-tools │ │
82+
│ pyspdxtools -i out.spdx.json │ │
83+
│ Or: https://tools.spdx.org │ │
84+
│ │
85+
└─────────────────────────────────────────────────────────────────────────────┘
86+
```
87+
88+
## 3. SBOM Generation Chain
89+
90+
When `bazel build //:my_sbom` is invoked, the following chain executes:
91+
92+
```
93+
┌──────────────────────────────────────────────────────────────────────┐
94+
│ PHASE 1: Loading (MODULE.bazel) │
95+
│ │
96+
│ sbom_metadata module extension iterates ALL modules in workspace: │
97+
│ - Collects sbom.license() tags (name, license, supplier, version) │
98+
│ - Collects sbom.license(type="cargo") tags (Rust crates) │
99+
│ - Writes metadata.json to @sbom_metadata repository │
100+
└──────────────────────────┬───────────────────────────────────────────┘
101+
102+
103+
┌──────────────────────────────────────────────────────────────────────┐
104+
│ PHASE 2: Analysis (aspect.bzl) │
105+
│ │
106+
│ sbom_aspect is attached to `targets` attr of sbom_rule. │
107+
│ For each target in targets = ["//src:app"]: │
108+
│ - Traverses deps, srcs, proc_macro_deps, hdrs, etc. │
109+
│ - Recursively collects SbomDepsInfo from all transitive deps │
110+
│ - Builds depsets of: │
111+
│ * external_repos (e.g. "score_kyron", "crates__tokio-1.10") │
112+
│ * transitive_deps (all labels in the dep graph) │
113+
└──────────────────────────┬───────────────────────────────────────────┘
114+
115+
116+
┌──────────────────────────────────────────────────────────────────────┐
117+
│ PHASE 3: Execution (rules.bzl → sbom_generator.py) │
118+
│ │
119+
│ _sbom_impl combines aspect output + extension metadata: │
120+
│ 1. Reads external_repos and transitive_deps from SbomDepsInfo │
121+
│ 2. Reads metadata.json from @sbom_metadata extension │
122+
│ 3. Writes _deps.json with all data + config │
123+
│ 4. Runs sbom_generator.py which: │
124+
│ a. Filters repos by exclude_patterns (removes build tools) │
125+
│ b. Resolves each repo to a component (name, version, PURL) │
126+
│ c. Merges extension metadata (license, supplier, version) │
127+
│ d. Calls spdx_formatter.py → {name}.spdx.json │
128+
│ e. Calls cyclonedx_formatter.py → {name}.cdx.json │
129+
└──────────────────────────────────────────────────────────────────────┘
130+
```
131+
132+
### Key files in the chain
133+
134+
| File | Phase | Role |
135+
|------|-------|------|
136+
| `extensions.bzl` | Loading | Collects `sbom.license()` from all modules (all dep types) |
137+
| `internal/aspect.bzl` | Analysis | Traverses target dep graph, returns `SbomDepsInfo` |
138+
| `internal/providers.bzl` | Analysis | Defines `SbomDepsInfo` provider (external_repos, transitive_deps) |
139+
| `internal/rules.bzl` | Execution | Joins aspect + extension data, invokes Python generator |
140+
| `internal/generator/sbom_generator.py` | Execution | Resolves repos to components, calls formatters |
141+
| `internal/generator/spdx_formatter.py` | Execution | Produces SPDX 2.3 JSON |
142+
| `internal/generator/cyclonedx_formatter.py` | Execution | Produces CycloneDX 1.6 JSON |
143+
| `internal/generator/purl.py` | Execution | Generates Package URLs for components |
144+
| `defs.bzl` | Public API | `sbom()` macro |
145+
146+
## 4. Tool Selection
147+
148+
### 4.1 Implemented Tool Stack
149+
150+
| Component | Tool Used | Status | Rationale |
151+
|-----------|-----------|--------|-----------|
152+
| SBOM Framework | Custom Bazel rules (aspects + module extension) | Implemented | Native Bazel integration, hermetic builds |
153+
| Dependency Discovery | Bazel aspect (sbom_aspect) | Implemented | Traverses transitive deps of any target |
154+
| Rust Crate Metadata | `sbom.license(type = "cargo")` in MODULE.bazel | Implemented | Manual license/supplier, auto PURL |
155+
| SPDX Generation | Custom Python formatter (spdx_formatter.py) | Implemented | SPDX 2.3 JSON, validated at tools.spdx.org |
156+
| CycloneDX Generation | Custom Python formatter (cyclonedx_formatter.py) | Implemented | CycloneDX 1.6 JSON |
157+
| License Data | `sbom.license()` in MODULE.bazel | Implemented | Manual declaration per dependency |
158+
| SPDX Validation | [spdx-tools](https://github.com/spdx/tools-python) (external) | Available | For offline validation |
159+
| License Compliance | Existing Dash (separate tool) | Existing | Complements SBOM, not integrated |
160+
161+
---

sbom/SBOM_Readme.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# SBOM Setup Guide
2+
3+
## 1. Configure MODULE.bazel
4+
5+
Add the following at the end of your `MODULE.bazel`:
6+
7+
```starlark
8+
# Load the SBOM extension and make the generated metadata repo available
9+
sbom_ext = use_extension("@score_tooling//sbom:extensions.bzl", "sbom_metadata")
10+
use_repo(sbom_ext, "sbom_metadata")
11+
12+
# Declare license/supplier for each dependency:
13+
14+
# For bazel_dep() modules — version is read from the module graph, no need to specify it:
15+
sbom_ext.license(name = "googletest", license = "BSD-3-Clause", supplier = "Google LLC")
16+
17+
# For http_archive deps — version is NOT in the module graph, must be specified:
18+
sbom_ext.license(name = "boost", license = "BSL-1.0", version = "1.87.0", supplier = "Boost.org")
19+
20+
# For git_override deps — specify version (commit) + remote so a PURL can be generated:
21+
sbom_ext.license(name = "iceoryx2", license = "Apache-2.0", supplier = "Eclipse Foundation",
22+
version = "d3d1c9a", remote = "https://github.com/eclipse-iceoryx/iceoryx2.git")
23+
24+
# For Rust crates (type = "cargo" generates pkg:cargo/ PURL):
25+
sbom_ext.license(name = "tokio", license = "MIT", version = "1.10", type = "cargo",
26+
supplier = "Tokio Contributors")
27+
```
28+
29+
## 2. Add SBOM target in BUILD
30+
31+
```starlark
32+
load("@score_tooling//sbom:defs.bzl", "sbom")
33+
34+
sbom(
35+
name = "my_sbom",
36+
targets = ["//my/app:binary"],
37+
component_name = "my_application",
38+
component_version = "1.0.0",
39+
)
40+
```
41+
42+
## 3. Build
43+
44+
```bash
45+
bazel build //:my_sbom
46+
```
47+
48+
## 4. Output
49+
50+
Two files in `bazel-bin/`:
51+
52+
- `my_sbom.spdx.json` -- SPDX 2.3
53+
- `my_sbom.cdx.json` -- CycloneDX 1.6
54+
55+
---
56+
57+
## Auto-extracted vs manual fields
58+
59+
**Always auto-extracted:**
60+
61+
| Field | Source |
62+
|-------|--------|
63+
| Dependency list | Aspect traverses transitive deps of your targets |
64+
| Version (bazel_dep) | From module graph |
65+
| Version (crates) | From crate repo name |
66+
| PURL | Generated from URLs/remotes |
67+
68+
**What is excluded from the SBOM:**
69+
70+
- Dependencies not in the transitive dep graph of your `targets` (e.g. `dev_dependency = True` lint/formatting tools that your binary never links against)
71+
- Build toolchain repos matching `exclude_patterns` (e.g. `rules_rust`, `rules_cc`, `bazel_tools`, `platforms`)
72+
73+
**What you must provide manually:**
74+
75+
| Field | Where | When |
76+
|-------|-------|------|
77+
| license | `sbom_ext.license()` | All dependencies |
78+
| supplier | `sbom_ext.license()` | Recommended for NTIA compliance |
79+
| version | `sbom_ext.license()` | For http_archive/git/crate deps (auto-extracted for bazel_dep) |
80+
81+
---
82+
83+
## Example
84+
85+
See `reference_integration/BUILD:39-66` for working SBOM targets and `reference_integration/MODULE.bazel:69-77` for the metadata extension setup.

0 commit comments

Comments
 (0)