Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement RFC 3553 to add SBOM support #13709

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

justahero
Copy link

@justahero justahero commented Apr 5, 2024

What does this PR try to resolve?

This PR is an implementation of RFC 3553 to add support to generate pre-cursor SBOM files for compiled artifacts in Cargo.

How should we test and review this PR?

The RFC 3553 adds a new option to Cargo to emit SBOM pre-cursor files. A project can be configured either by the new Cargo config field sbom.

# .cargo/config.toml
[build]
sbom = true

or using the environment variable CARGO_BUILD_SBOM=true. The sbom option is an unstable feature and requires the -Zsbom flag to enable it.

Check out this branch & compile Cargo. Pick a Cargo project to test it on, then run:

CARGO_BUILD_SBOM=true <path/to/compiled/cargo>/target/debug/cargo build -Zsbom

All generated *.cargo-sbom.json files are located in the target folder alongside their artifacts. To list all generated files use:

find ./target -name "*.cargo-sbom.json"

then check their content. To see the current output format, see these examples.

Additional information

There are a few things that I would like to get feedback on, in particular the generated JSON format is not final. Currently it holds the information listed in the RFC 3553, but it could be further enriched with information only available during builds.

During the implementation a number of questions arose.

  • Is using the UnitGraph the right structure to determine all dependencies?
  • Is the location in the compile method to generate the SBOM files appropriate?
  • Is there important information missing from the generated output, some fields that should be omitted?
  • How best to check JSON output in the related tests in testsuite, are useful tests missing?
  • Are custom build script dependencies correctly resolved?

Thanks @arlosi, @RobJellinghaus and @lfrancke for initial guidance & feedback.

@rustbot
Copy link
Collaborator

rustbot commented Apr 5, 2024

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @ehuss (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added A-build-execution Area: anything dealing with executing the compiler A-configuration Area: cargo config files and env vars A-unstable Area: nightly unstable support S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 5, 2024
/// Returns the list of SBOM output file paths for a given Unit.
///
/// Only call this function when `sbom` is active.
pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm uncertain if this function should return a Vec or if a single PathBuf is actually sufficient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might output several different crate-types, e.g. rlib, cdylib.

@heisen-li
Copy link
Contributor

Much respect for your contribution.

From my kind reminders, it seems appropriate to modify the documentation of the corresponding sections, e.g. Configuration, Environment Variables.

@weihanglo
Copy link
Member

Thanks for the reminder, @heisen-li. Would love to see a doc update, though we should probably focus on the design discussion first, as the location of the configuration is not yet decided. (See rust-lang/rfcs#3553 (comment)).

@epage
Copy link
Contributor

epage commented Apr 9, 2024

One approach for the docs (if this is looking to be merged) is to put the env and config documentation fragments in the Unstable docs.

Similar to the generation of `depinfo` files, a function is called to
generated SBOM precursor file named `output_sbom`. It takes the
`BuildRunner` & the current `Unit`. The `sbom` flag can be specified as
a cargo build option, but it's currently not configured correctly. To
test the generation the flag is set to `true`.

This passes in the cargo build config `sbom`.
This ignores dependencies for custom build scripts. The output should be
similar to what `cargo tree` reports.
This is similar to what the `cargo metadata` command outputs.
This extracts the logic to get the list of SBOM output file paths into
its own function in `BuildRunner` for a given Unit.
* extract sbom config into helper function
Still needs to check output.
@justahero justahero force-pushed the rfc3553/cargo-sbom-support branch from 190682e to ae0881c Compare May 2, 2024 19:54
base.arg("-Z").arg("binary-dep-depinfo");
}

if is_primary {
base.env("CARGO_PRIMARY_PACKAGE", "1");

if gctx.cli_unstable().sbom && build_runner.bcx.build_config.sbom {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, paths are better to be joined using semi-colons, maybe via std::env::join_paths, to maximize the compatibility.

/// Returns the list of SBOM output file paths for a given Unit.
///
/// Only call this function when `sbom` is active.
pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might output several different crate-types, e.g. rlib, cdylib.

.iter()
.filter(|o| matches!(o.flavor, FileFlavor::Normal | FileFlavor::Linkable))
.filter_map(|output_file| output_file.hardlink.as_ref())
.map(|link_dst| link_dst.with_extension("cargo-sbom.json"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to handle name collisions here? For example dylibs might collide with each their as their name have no -<hash> suffix.

See #6313 for some details.

//! cargo-sbom precursor files for external tools to create SBOM files from.
//! See [`output_sbom`] for more.

use std::{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in Cargo we don't do nested multi-line import, though this is not enforced :)

assert!(p.bin("foo").with_extension("cargo-sbom.json").is_file());
assert_eq!(
1,
p.glob(p.target_debug_dir().join("libfoo.cargo-sbom.json"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we might need to deal with different naming convention on different platform. (Windows specifically?)

build = "build.rs"

[build-dependencies]
cc = "1.0.46"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should avoid introducing external crates.io dependency. Use something like

    Package::new("baz", "0.0.1").publish();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see some UI test for the JSON output. It's easier to check what's inside the SBOM file.

https://doc.crates.io/contrib/tests/writing.html#ui-tests

Copy link
Member

@weihanglo weihanglo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just note that I reviewed this as-is, didn't really think too much for the design itself. Thank you for working on this!

@@ -102,6 +104,16 @@ impl BuildConfig {
anyhow::bail!("-Zbuild-std requires --target");
}

// If sbom flag is set, it requires the unstable feature
let sbom = match gctx.get_env_os("CARGO_BUILD_SBOM") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was mentioned in office hours. Just wrote it down for reference. GlobalContext::get_bool could cover both env and config.

Comment on lines +113 to +115
if sbom && !gctx.cli_unstable().sbom {
anyhow::bail!("Cargo build config 'sbom' is unstable; pass `-Zsbom` to enable it");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a soft error (warning) is more friendly and won't block people using the same config from different toolchain. Something like

self.gctx()
.shell()
.warn("ignoring `resolver` config table without `-Zmsrv-policy`")?;

(And provide a link to that unstable documentation when we have one)

let sbom = Sbom::new(unit, packages.clone(), rustc.clone());

let mut outfile = BufWriter::new(paths::create(sbom_output_file)?);
let output = serde_json::to_string_pretty(&sbom)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps pretty not important at this moment. Just wonder how big the size of the output sbom would be, and at some point we might want to use serde_json::to_writer_pretty instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-build-execution Area: anything dealing with executing the compiler A-configuration Area: cargo config files and env vars A-unstable Area: nightly unstable support S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants