Skip to content

Commit eb2a3c2

Browse files
committed
add support for extra sbom information in Cargo.toml
The schema for Cargo.toml allows arbitrary extra data to be stored under the `[package.metadata]` table. It is convention to create a subtable under this (e.g. `[package.metadata.cyclonedx]`), and then to read/write data in this table for your custom use-case. This PR uses the `[package.metadata]` table to store more SBOM information for a crate. In particular, it adds support for the `modified` field on components (which is what my project needs). I could add support for other fields as required. The name of the subtable is customizable from a CLI argument, and by default is empty, meaning no attempt is made to access the data. I also made a small change to a test to write a main function in a `main.rs` file. It's not essential to the patch and I can remove it if requested.
1 parent d5c35fd commit eb2a3c2

6 files changed

Lines changed: 78 additions & 2 deletions

File tree

cargo-cyclonedx/src/cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ Defaults to the host target, as printed by 'rustc -vV'"
108108
/// Do not include build-time dependencies in the SBOM
109109
#[clap(long = "no-build-deps")]
110110
pub no_build_deps: bool,
111+
112+
/// Look for extra SBOM information in this subtable of `[package.metadata]`
113+
#[clap(long = "metadata-section-name")]
114+
pub package_metadata_subtable: Option<String>,
111115
}
112116

113117
impl Args {
@@ -193,6 +197,7 @@ impl Args {
193197
describe,
194198
spec_version,
195199
only_normal_deps,
200+
package_metadata_subtable: self.package_metadata_subtable.clone(),
196201
})
197202
}
198203
}

cargo-cyclonedx/src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub struct SbomConfig {
3434
pub describe: Option<Describe>,
3535
pub spec_version: Option<SpecVersion>,
3636
pub only_normal_deps: Option<bool>,
37+
/// An extra section under `[package.metadata]` for data that is not stored
38+
/// in Cargo.toml
39+
pub package_metadata_subtable: Option<String>,
3740
}
3841

3942
impl SbomConfig {
@@ -60,6 +63,10 @@ impl SbomConfig {
6063
describe: other.describe.or(self.describe),
6164
spec_version: other.spec_version.or(self.spec_version),
6265
only_normal_deps: other.only_normal_deps.or(self.only_normal_deps),
66+
package_metadata_subtable: other
67+
.package_metadata_subtable
68+
.clone()
69+
.or_else(|| self.package_metadata_subtable.clone()),
6370
}
6471
}
6572

cargo-cyclonedx/src/generator.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ impl SbomGenerator {
259259
component.author = Some(NormalizedString::new(&package.authors.join(", ")));
260260
}
261261

262+
self.add_metadata_subtable(package, &mut component);
263+
262264
component
263265
}
264266

@@ -513,6 +515,29 @@ impl SbomGenerator {
513515
}
514516
}
515517

518+
fn add_metadata_subtable(&self, package: &Package, component: &mut Component) {
519+
let Some(table_name) = self.config.package_metadata_subtable.as_ref() else {
520+
// No `[package.metadata]` subtable, so don't try to add any more
521+
// information
522+
return;
523+
};
524+
525+
let Some(table) = package.metadata.get(table_name) else {
526+
log::warn!("could not find metadata table called {table_name}");
527+
return;
528+
};
529+
if let Some(modified) = table.get("modified") {
530+
if let Some(modified) = modified.as_bool() {
531+
component.modified = Some(modified);
532+
} else {
533+
log::warn!("`modified` field in `{table_name}` table was not a boolean, ignoring");
534+
}
535+
}
536+
// TODO support for other fields not covered by the Cargo.toml format could be added here.
537+
// TODO serde could be used to parse this table into a struct to reduce need for manual
538+
// deserialization.
539+
}
540+
516541
fn create_metadata(
517542
&self,
518543
package: &Package,

cargo-cyclonedx/src/main.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,40 @@ mod tests {
247247
!= NormalizedString::new("runtime_dep_of_build_dep")
248248
|| c.scope == Some(Scope::Excluded)));
249249
}
250+
251+
#[test]
252+
fn parse_toml_with_metadata_table() {
253+
use crate::cli;
254+
use crate::generate_sboms;
255+
use clap::Parser;
256+
use cyclonedx_bom::models::component::Scope;
257+
use std::path::PathBuf;
258+
259+
let mut test_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
260+
test_cargo_toml.push("tests/fixtures/build_then_runtime_dep/Cargo.toml");
261+
262+
let path_arg = &format!("--manifest-path={}", test_cargo_toml.display());
263+
let args = [
264+
"cyclonedx",
265+
path_arg,
266+
"--no-build-deps",
267+
"--metadata-section-name=cyclonedx",
268+
];
269+
let args_parsed = cli::Args::parse_from(args.iter());
270+
271+
let sboms = generate_sboms(&args_parsed).unwrap();
272+
273+
let components = sboms[0].bom.components.as_ref().unwrap();
274+
assert!(components
275+
.0
276+
.iter()
277+
.all(|f| f.scope == Some(Scope::Required)));
278+
assert!(components.0.iter().all(|f| {
279+
if f.name == "top_level_crate".into() {
280+
f.modified == Some(true)
281+
} else {
282+
f.modified == None
283+
}
284+
}));
285+
}
250286
}

cargo-cyclonedx/tests/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ fn find_content_in_stderr() -> Result<(), Box<dyn std::error::Error>> {
135135

136136
fn make_temp_rust_project() -> Result<assert_fs::TempDir, assert_fs::fixture::FixtureError> {
137137
let tmp_dir = assert_fs::TempDir::new()?;
138-
tmp_dir.child("src/main.rs").touch()?;
138+
tmp_dir.child("src/main.rs").write_str("fn main() {}")?;
139139

140140
tmp_dir
141141
.child("Cargo.toml")

cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[build-dependencies]
9-
build_dep = {path = "../build_dep"}
9+
build_dep = {path = "../build_dep"}
10+
11+
[package.metadata.cyclonedx]
12+
modified = true

0 commit comments

Comments
 (0)