diff --git a/.github/workflows/CODEOWNERS b/.github/workflows/CODEOWNERS new file mode 100644 index 0000000..1c60e06 --- /dev/null +++ b/.github/workflows/CODEOWNERS @@ -0,0 +1 @@ +@djschleen @mirxcle \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 0faed30..c594485 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -90,7 +90,7 @@ { "type": "lldb", "request": "launch", - "name": "Debug executable 'trustier' (juiceshop)", + "name": "Debug executable 'trustier' --strict (juiceshop)", "cargo": { "args": [ "build", @@ -103,6 +103,7 @@ } }, "args": [ + "--strict", "./tests/_TESTDATA_/juiceshop.cyclonedx.json" ], "cwd": "${workspaceFolder}" diff --git a/Cargo.toml b/Cargo.toml index a2fb306..b749fa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "trustier" -version = "0.1.0" +version = "0.1.1" edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -clap = { version = "4.5.17", features = ["derive"] } +clap = { version = "4.5.20", features = ["derive"] } cyclonedx-bom = "0.7.0" surf = "2.3.2" async-std = "1.13.0" diff --git a/README.md b/README.md index 12f1403..8213391 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ ## Table of Contents - [Overview](#overview) -- [The importance of undetstanding supply chain attacks](#the-importance-of-undetstanding-supply-chain-attacks) +- [The importance of understanding supply chain attacks](#the-importance-of-understanding-supply-chain-attacks) - [Installation](#installation) -- [Example Usage](#example-usage) - [Application Arguments](#application-arguments) +- [Example Usage](#example-usage) - [Troubleshooting](#troubleshooting) + - [SBOM Validation](#sbom-validation) + - [Supported CycloneDX versions](#supported-cyclonedx-versions) - [Credits](#credits) ## Overview @@ -59,16 +61,16 @@ Sources: Binaries for Mac, Linux, and Windows Platforms are available from the [Releases](https://github.com/devops-kung-fu/trustier/releases) section of this repsitory. Download, unpack, and use! -__NOTE:__ The application has not been tested on all platforms and architectures. If you experience any issues, please report them [here]( -https://github.com/devops-kung-fu/trustier/issues) +**NOTE:** The application has not been tested on all platforms and architectures. If you experience any issues, please report them [here](https://github.com/devops-kung-fu/trustier/issues) ## Application Arguments -| Argument | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------------- | -| `` | The SBOM (Software Bill of Materials) to process. This argument is required. | -| `--ratelimit ` | Optional time in milliseconds to pause before making requests to https://trustypkg.dev. Defaults to 500 ms. | -| `--output_file ` | Optional file name to write JSON output to. If not provided, output will be printed to the console. | +| Argument | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `` | The SBOM (Software Bill of Materials) to process. This argument is required. | +| `--ratelimit ` | Optional time in milliseconds to pause before making requests to https://trustypkg.dev. Defaults to 500 ms. | +| `--output_file ` | Optional file name to write JSON output to. If not provided, output will be printed to the console. | +| `--strict` | If set, will perform a strict SBOM validation, otherwise `trustier` will attempt to process the SBOM. Optional and defaults to false | ## Example Usage @@ -82,6 +84,9 @@ trustier sbom_file.json --ratelimit 1000 # Optional output_file argument trustier sbom_file.json --output_file output.json +# Perform strict SBOM validation +trustier sbom_file.json --strict --output_file output.json + # Takes an SBOM from STDIN and outputs JSON without any console decoration cat sbom_file.json | trustier - @@ -91,13 +96,24 @@ cat sbom_file.json | trustier - ## Troubleshooting -During testing, we found there were some required fields needed in the SBOM in order to be considered valid. Ensure at minimum you have the following fields in your components: +### SBOM Validation + +During testing, we found there were some required fields needed in the SBOM in order to be considered valid. We +utilized the validation logic provided in the CycloneDX dependencies we use in `trustier`. In version 0.1.1, we +disabled validation by default, but if you wish to utilize strict validation then utilize the `--strict` flag. + +Ensure at minimum you have the following fields in your components if you are using strict validation: - `name` - `purl` - `type` -__NOTE:__ `trustier` does not support SPDX formatted SBOMS at this time. +### Supported CycloneDX versions + +`trustier` relies on [CycloneDX](https://github.com/CycloneDX/cyclonedx-rust-cargo/blob/main/cyclonedx-bom/README.md) to +load and work with SBOMs. This provides a limitation of only supporting versions 1.3, 1.4, and 1.5 of the CycloneDX specification at this time. + +**NOTE:** `trustier` does not support SPDX formatted SBOMS at this time. ## Credits diff --git a/src/main.rs b/src/main.rs index 12434bf..80e0fa0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,10 @@ struct Args { //Optional file name to write json output to #[arg(short, long, required = false)] output_file: Option, + + //Optional flag to enable strict SBOM validation + #[arg(short, long, default_value_t = false)] + strict: bool, } fn main() { @@ -40,12 +44,7 @@ fn main() { if args.sbom.is_file() { print_ascii_header(); - } - - if args.sbom.is_file() { - conditional_println!(args.sbom.is_file(), "* Reading SBOM from file..."); - } else { - conditional_println!(args.sbom.is_file(), "* Reading SBOM from stdin..."); + conditional_println!(true, "* Reading SBOM from file..."); } let file_contents = match args.sbom.clone().into_reader() { @@ -62,17 +61,21 @@ fn main() { let bom = match Bom::parse_from_json_v1_5(file_contents) { Ok(bom) => bom, Err(e) => { - eprintln!("Error parsing SBOM: {}", e); + eprintln!("* Error parsing SBOM! \n\n{}", e); return; } }; - if !bom.validate().passed() { - eprintln!("* Provided input is not a valid SBOM"); - return; + if args.strict { + conditional_println!(args.sbom.is_file(), "* strict SBOM checking enabled..."); + if !bom.validate().passed() { + eprintln!("* Provided input is not a valid SBOM"); + return; + } else { + conditional_println!(args.sbom.is_file(), "* SBOM is valid"); + } } - conditional_println!(args.sbom.is_file(), "* SBOM is valid"); if let Some(serial_number) = &bom.serial_number { conditional_println!( args.sbom.is_file(), @@ -141,7 +144,8 @@ async fn process_sbom( collected_purls.len() ); } else { - conditional_println!(args.sbom.is_file(), "* Nothing to do...\n") + conditional_println!(args.sbom.is_file(), "* Nothing to do...\n"); + return Ok(()); } let responses = fetch_purl_bodies(&collected_purls, args.ratelimit).await?; @@ -156,7 +160,7 @@ async fn process_sbom( } } fs::write(of_clone, json).expect("Failed to write JSON to file"); - conditional_println!(args.sbom.is_file(), "\n* JSON written to file: {}\n", of); + conditional_println!(args.sbom.is_file(), "* JSON written to file: {}\n", of); } else { let json = serde_json::to_string_pretty(&responses).unwrap(); println!("{}", json);