Update: moved to RFC
Please make further comments on the RFC itself
This is an initial draft at getting precise dependency information from Cargo for each compiled output. There are already a number of Cargo extensions (auditable, cyclonedx, bom) that could consume this information rather than reconstructing themselves.
I'm particularly interested in what information people need for an SBOM and whether this would be sufficient. This SBOM file generated by Cargo is intended as an intermediate format that could be processed by other tools into standardized formats such as CycloneDX, or SPDX. Feedback from authors & contributors of existing tools like cargo-cyclonedx would also be very helpful.
Cargo SBOM pre-RFC
- Feature Name:
cargo_sbom - Start Date: 2023-11-01
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
Summary
This RFC adds an option to Cargo that emits a Software Bill of Materials (SBOM) alongside compiled artifacts. Similar to how Cargo emits split debug info or "dep-info" (.d) files, this change emits an SBOM in a Cargo-specific format alongside outputs in the target directory. External tooling can consume this Cargo SBOM file and transform it into other SBOM formats such as SPDX or CycloneDX.
Motivation
A SBOM (software bill of materials) is a list of all components and dependencies used to build a piece of software. The two leading SBOM formats being adopted by industry are SPDX and CycloneDX. Both are still evolving and have multiple specification versions & data formats (JSON, XML).
New government initiatives aimed at improving the security of the software supply chain such as the US "Executive Order on Improving the Nation's Cybersecurity" or the EU "Cyber Resilience Act" require a Software Bill of Materials. Generating accurate SBOMs with Cargo is currently difficult because, depending on target selection or activated features, the dependencies may be different.
For workspaces that generate multiple compiled artifacts, each artifact may have different dependencies referenced. Existing tools (see prior art section) attempt to approximate the correct dependency set, however precise dependency information for each compiled artifact is difficult without built-in Cargo support. Generating the SBOM at the same time as the compiled artifact allows precise dependency information to be emitted for each compiled artifact.
Guide-level explanation
The generation of SBOM information is controlled by Cargo's configuration. To enable SBOM generation, set the following configuration:
[build]
sbom = true
Or use the environment variable CARGO_BUILD_SBOM=true.
If enabled, an SBOM file will be placed next to each compiled artifact for bin, staticlib cdylib crate types in the target directory with the name <crate_name>.cargo-sbom.json. The SBOM will contain information about dependencies used to build the compiled artifact.
Reference-level explanation
The SBOM file generated by Cargo is not intended as a final SBOM artifact, but rather a precursor. Post-processing tooling can use the information produced here as part of building a final SBOM.
The SBOM file will be written to disk before rustc is executed for the each artifact. This enables RUSTC_WORKSPACE_WRAPPER to point at a program that can utilize the SBOM file to embed information into the binary if desired.
Format
The format will use JSON, but the exact format is not specified in this RFC. Additional fields can be added as needed.
Resolved Dependency Tree
The SBOM will include the following information (if available) for each crate:
- ID (opaque identifier)
- Name
- Version
- Source (registry / git / path etc.)
- Checksum
- Dependencies (list of IDs)
- Type (normal, build)
- Activated features
If a crate is used as both a normal dependency and a build dependency that is separately compiled, then separate entries will exist in the dependency tree with the correct activated features listed for each instance.
Checksum is an optional field, since only crates from registries have checksums. If a checksum is needed for a crate that comes from a path dependency for example, it will be up to the post-processing tool to produce an appropriate value.
If further information is needed (such as license), then the post-processing tool can use cargo metadata or another mechanism to find it.
Resolved build confugration
- Rust toolchain version
RUSTFLAGS- Current build profile name
- Selected profile values
Drawbacks
It introduces yet another SBOM format. However, the format is specifically designed to be used as an intermediate, to be converted to an industry-standard format by external tooling.
Rationale and alternatives
Since there is no consensus on a single SBOM format within the software industry, and existing formats are still evolving, Cargo should not pick an existing SBOM format. If Cargo were to use existing SBOM formats, multiple formats (and multiple versions of each format) would need to be supported. The task of generating a specific SBOM format is best left to applications outside Cargo or Cargo extension.
Unfortunately it's difficult to extract accurate SBOM information with existing options. Using the Cargo.lock file or cargo metadata overincludes dependencies. Additionally, since Cargo has many different commands that produce compiled artifacts (build, test, bench, etc.) and each of these commands take arguments that can affect the dependency list it's difficult to ensure that the correct dependency list is used.
Adding an option to cargo metadata to support resolver v2 would help with overinclusion of dependencies, but still makes it difficult to ensure the exact set of features, command-line arguments, and other options are taken into account. Additionally, since build scripts (build.rs) may impact the output, cargo metadata may need to execute them.
Another alternative is to extract information by setting the RUSTC_WRAPPER environment variable, then capture feature flags and dependencies via a wrapper tool. This would require the wrapper tool to parse the rustc command line arguments to capture the set of feature flags and referenced dependencies. This approach would prevent other uses of RUSTC_WRAPPER, as well as being potentially fragile.
Prior art
- RFC2801: proposes embedding dependency information directly into the binary. Implemented as the
cargo auditableextension. - cargo-auditable: Cargo extension that embeds a subset of the information described in this RFC directly into the binary. The JSON format used by this RFC could be based on the cargo-auditable format.
- cargo-cyclonedx: Cargo extension to generate a CycloneDX SBOM.
- cargo-bom: Cargo extension to generate a BOM in an ASCII format including license information.
- cargo build-plan (#5579): provides an option to emit a JSON representation of the commands to execute, without actually running them. This option has poor integration with
build.rsand was planned for deletion in 2018. - cargo unit graph (#8002): Very similar to what this RFC intends on writing to disk. Since this runs separately from the build, it cannot take
build.rsoutput into account.
Unresolved questions
The exact specifics about what will be included in the SBOM and the specific JSON format are subject to change during the implementation of the RFC.
Future possibilities
Industry standard format
If the software industry converges on a single, stable SBOM format, Cargo could directly emit it. The existing SBOM formats are currently changing too much at this time to standardize on a specific format.
Additional fields can be added to the SBOM without a breaking change.
Build scripts
Build scripts could communicate back to Cargo to inject additional dependencies into the SBOM. For example, if a crate builds c code and then links with it, it could emit a message that causes Cargo to read in a file describing the c dependency.
cargo::sbom=<PATH>
Cargo would then include the additional dependency information in the SBOM graph.
Embedding dependency information into binaries
The implementation of RFC2801 could be based on the information provided by this RFC.