mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-05-26 01:17:51 +08:00
feat(build): add SPDX 2.3 SBOM generation for builds (#26731)
This commit is contained in:
@@ -906,6 +906,7 @@
|
||||
- [Translation](contribute/translation.md)
|
||||
- [Terminology/Notation](contribute/notation.md)
|
||||
- [Licenses](contribute/licenses.md)
|
||||
- [SBOM](contribute/sbom.md)
|
||||
- [Releases](releases/index.md)
|
||||
- [Release Process](releases/release_process.md)
|
||||
- [main (alpha)](releases/main.md)
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
# Software Bill of Materials (SBOM)
|
||||
|
||||
PX4 generates a [Software Bill of Materials](https://ntia.gov/SBOM) for every firmware build in [SPDX 2.3](https://spdx.github.io/spdx-spec/v2.3/) JSON format.
|
||||
|
||||
## Why SBOM?
|
||||
|
||||
- **Regulatory compliance**: The EU Cyber Resilience Act (CRA) requires SBOMs for products with digital elements (reporting obligations begin in September 2026).
|
||||
- **Supply chain transparency**: SBOMs enumerate every component compiled into firmware, enabling users and integrators to audit dependencies.
|
||||
- **NTIA minimum elements**: Each SBOM satisfies all seven [NTIA required fields](https://www.ntia.gov/report/2021/minimum-elements-software-bill-materials-sbom): supplier, component name, version, unique identifier, dependency relationship, author, and timestamp.
|
||||
|
||||
## Format
|
||||
|
||||
PX4 uses SPDX 2.3 JSON.
|
||||
SPDX is the Linux Foundation's own standard (ISO/IEC 5962), aligning with PX4's position as a Dronecode/LF project.
|
||||
Zephyr RTOS also uses SPDX.
|
||||
|
||||
Each SBOM contains:
|
||||
|
||||
- **Primary package**: The PX4 firmware for a specific board target, marked with `primaryPackagePurpose: FIRMWARE`.
|
||||
- **Git submodules**: All third-party libraries included via git submodules (~33 packages), with SPDX license identifiers and commit hashes.
|
||||
- **Python build dependencies**: Packages from `Tools/setup/requirements.txt` marked as `BUILD_DEPENDENCY_OF` the firmware.
|
||||
- **Board-specific modules**: Internal PX4 modules compiled for the target board.
|
||||
- **Compiler**: The C compiler used for the build.
|
||||
|
||||
Typical SBOM size: 70-100 packages, ~500 lines, ~20 KB JSON.
|
||||
|
||||
## Generation
|
||||
|
||||
SBOMs are generated automatically as part of every CMake build.
|
||||
The output file is:
|
||||
|
||||
```txt
|
||||
build/<target>/<target>.sbom.spdx.json
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```txt
|
||||
build/px4_fmu-v6x_default/px4_fmu-v6x_default.sbom.spdx.json
|
||||
```
|
||||
|
||||
The generator script is `Tools/ci/generate_sbom.py`.
|
||||
It requires PyYAML (`pyyaml`) for loading license overrides.
|
||||
|
||||
### CMake Integration
|
||||
|
||||
The `sbom` CMake target is included in the default `ALL` target.
|
||||
The relevant CMake module is `cmake/sbom.cmake`.
|
||||
|
||||
### Disabling SBOM Generation
|
||||
|
||||
Set the environment variable before building.
|
||||
This is checked at CMake configure time, so a clean build or reconfigure is required:
|
||||
|
||||
```sh
|
||||
PX4_SBOM_DISABLE=1 make px4_fmu-v6x_default
|
||||
```
|
||||
|
||||
If the build directory already exists, force a reconfigure:
|
||||
|
||||
```sh
|
||||
PX4_SBOM_DISABLE=1 cmake -B build/px4_fmu-v6x_default .
|
||||
```
|
||||
|
||||
### Manual Generation
|
||||
|
||||
You can also run the generator directly:
|
||||
|
||||
```sh
|
||||
python3 Tools/ci/generate_sbom.py \
|
||||
--source-dir . \
|
||||
--board px4_fmu-v6x_default \
|
||||
--modules-file build/px4_fmu-v6x_default/config_module_list.txt \
|
||||
--compiler arm-none-eabi-gcc \
|
||||
--output build/px4_fmu-v6x_default/px4_fmu-v6x_default.sbom.spdx.json
|
||||
```
|
||||
|
||||
## Artifacts
|
||||
|
||||
SBOMs are available in:
|
||||
|
||||
| Location | Path |
|
||||
| --------------- | ---------------------------------------- |
|
||||
| Build directory | `build/<target>/<target>.sbom.spdx.json` |
|
||||
| GitHub Releases | Alongside `.px4` firmware files |
|
||||
| S3 | Same directory as firmware artifacts |
|
||||
|
||||
## Validation
|
||||
|
||||
Validate an SBOM against the SPDX JSON schema:
|
||||
|
||||
```sh
|
||||
python3 -c "
|
||||
import json
|
||||
doc = json.load(open('build/px4_sitl_default/px4_sitl_default.sbom.spdx.json'))
|
||||
assert doc['spdxVersion'] == 'SPDX-2.3'
|
||||
assert doc['dataLicense'] == 'CC0-1.0'
|
||||
assert len(doc['packages']) > 0
|
||||
print(f'Valid: {len(doc[\"packages\"])} packages')
|
||||
"
|
||||
```
|
||||
|
||||
For full schema validation, use the [SPDX online validator](https://tools.spdx.org/app/validate/) or the `spdx-tools` CLI.
|
||||
|
||||
## License Detection
|
||||
|
||||
Submodule licenses are identified through a combination of auto-detection and manual overrides.
|
||||
|
||||
### Auto-Detection
|
||||
|
||||
The generator reads the first 100 lines of each submodule's LICENSE or COPYING file
|
||||
and matches keywords against known patterns.
|
||||
Copyleft licenses (GPL, LGPL, AGPL) are checked before permissive ones
|
||||
to prevent false positives.
|
||||
|
||||
Supported patterns include:
|
||||
|
||||
| SPDX Identifier | Matched Keywords |
|
||||
| --------------- | ----------------------------------------------------- |
|
||||
| GPL-3.0-only | "GNU GENERAL PUBLIC LICENSE", "Version 3" |
|
||||
| GPL-2.0-only | "GNU GENERAL PUBLIC LICENSE", "Version 2" |
|
||||
| LGPL-3.0-only | "GNU LESSER GENERAL PUBLIC LICENSE", "Version 3" |
|
||||
| LGPL-2.1-only | "GNU Lesser General Public License", "Version 2.1" |
|
||||
| AGPL-3.0-only | "GNU AFFERO GENERAL PUBLIC LICENSE", "Version 3" |
|
||||
| Apache-2.0 | "Apache License", "Version 2.0" |
|
||||
| MIT | "Permission is hereby granted" |
|
||||
| BSD-3-Clause | "Redistribution and use", "Neither the name" |
|
||||
| BSD-2-Clause | "Redistribution and use", "THIS SOFTWARE IS PROVIDED" |
|
||||
| ISC | "Permission to use, copy, modify, and/or distribute" |
|
||||
| EPL-2.0 | "Eclipse Public License", "2.0" |
|
||||
| Unlicense | "The Unlicense", "unlicense.org" |
|
||||
|
||||
If no pattern matches, the license is set to `NOASSERTION`.
|
||||
|
||||
### Override File
|
||||
|
||||
When auto-detection fails or returns the wrong result,
|
||||
add an entry to `Tools/ci/license-overrides.yaml`:
|
||||
|
||||
```yaml
|
||||
overrides:
|
||||
src/lib/crypto/libtomcrypt:
|
||||
license: "Unlicense"
|
||||
comment: "Public domain dedication. Functionally equivalent to Unlicense."
|
||||
```
|
||||
|
||||
Each entry maps a submodule path to its correct SPDX license identifier.
|
||||
The optional `comment` field is emitted as `licenseComments` in the SBOM,
|
||||
providing context for auditors reviewing complex licensing situations
|
||||
(dual licenses, composite LICENSE files, public domain dedications).
|
||||
|
||||
### Copyleft Guardrail
|
||||
|
||||
The `--verify-licenses` command flags submodules with copyleft licenses
|
||||
(GPL, LGPL, AGPL) in a dedicated warning section.
|
||||
This is informational only and does not cause a failure.
|
||||
It helps maintainers track copyleft obligations when adding new submodules.
|
||||
|
||||
### Platform Filtering
|
||||
|
||||
Submodules under `platforms/nuttx/` are excluded from POSIX and QURT SBOMs.
|
||||
The `--platform` argument (set automatically by CMake via `${PX4_PLATFORM}`)
|
||||
controls which platform-specific submodules are included.
|
||||
This ensures SITL builds do not list NuttX RTOS packages.
|
||||
|
||||
### Verification
|
||||
|
||||
Run the verify command to check detection for all submodules:
|
||||
|
||||
```sh
|
||||
python3 Tools/ci/generate_sbom.py --verify-licenses --source-dir .
|
||||
```
|
||||
|
||||
This prints each submodule with its detected license, any override, and the final value.
|
||||
It exits non-zero if any checked-out submodule resolves to `NOASSERTION` without an override.
|
||||
Copyleft warnings are printed after the main table.
|
||||
|
||||
### Adding a New Submodule
|
||||
|
||||
1. Add the submodule normally.
|
||||
2. Run `--verify-licenses` to confirm the license is detected.
|
||||
3. If detection fails, add an override to `Tools/ci/license-overrides.yaml`.
|
||||
4. If the license is not in the SPDX list, use `LicenseRef-<name>`.
|
||||
|
||||
### EU CRA Compliance
|
||||
|
||||
The EU Cyber Resilience Act requires SBOMs for products with digital elements.
|
||||
The goal is zero `NOASSERTION` licenses in shipped firmware SBOMs.
|
||||
Every submodule should have either a detected or overridden license.
|
||||
The `--verify-licenses` check enforces this in CI.
|
||||
|
||||
## What's in an SBOM
|
||||
|
||||
This section is for integrators, compliance teams, and anyone reviewing SBOM artifacts.
|
||||
|
||||
### Where to Find SBOMs
|
||||
|
||||
| Location | Path |
|
||||
| --------------- | ---------------------------------------- |
|
||||
| Build directory | `build/<target>/<target>.sbom.spdx.json` |
|
||||
| GitHub Releases | Alongside `.px4` firmware files |
|
||||
| S3 | Same directory as firmware artifacts |
|
||||
|
||||
### Reading the JSON
|
||||
|
||||
Each SBOM is a single JSON document following SPDX 2.3.
|
||||
Key fields:
|
||||
|
||||
- **`packages`**: Array of all components. Each has `name`, `versionInfo`, `licenseConcluded`, and `SPDXID`.
|
||||
- **`relationships`**: How packages relate. `CONTAINS` means a submodule is compiled into firmware. `BUILD_DEPENDENCY_OF` means a tool used only during build.
|
||||
- **`licenseConcluded`**: The SPDX license identifier determined for that package.
|
||||
- **`licenseComments`**: Free-text explanation for complex cases (dual licenses, composite files, public domain).
|
||||
- **`externalRefs`**: Package URLs (purls) linking to GitHub repos or PyPI.
|
||||
|
||||
### Understanding NOASSERTION
|
||||
|
||||
`NOASSERTION` means no license could be determined.
|
||||
For submodules, this happens when:
|
||||
|
||||
- The submodule is not checked out (common in CI shallow clones).
|
||||
- No LICENSE/COPYING file exists.
|
||||
- The LICENSE file does not match any known pattern and no override is configured.
|
||||
|
||||
For shipped firmware, `NOASSERTION` should be resolved by adding an override.
|
||||
For build-only dependencies (Python packages), `NOASSERTION` is acceptable
|
||||
since these are not compiled into the firmware binary.
|
||||
Reference in New Issue
Block a user