diff options
author | Leonardo Neumann <leonardo@neumann.dev.br> | 2021-08-14 18:05:37 -0300 |
---|---|---|
committer | Georgy Yakovlev <gyakovlev@gentoo.org> | 2021-08-26 00:40:36 -0700 |
commit | 1f8c7eb03ace33caba40822651c822d140f2840c (patch) | |
tree | c90a99f3208f03956d1ef9ffc1c6010d1ebb5c53 | |
parent | Use path references instead of owned counterparts (diff) | |
download | cargo-ebuild-1f8c7eb03ace33caba40822651c822d140f2840c.tar.gz cargo-ebuild-1f8c7eb03ace33caba40822651c822d140f2840c.tar.bz2 cargo-ebuild-1f8c7eb03ace33caba40822651c822d140f2840c.zip |
Implement audit functionality using rustsec
Closes: https://github.com/gentoo/cargo-ebuild/pull/15
Closes: https://github.com/gentoo/cargo-ebuild/issues/2
Signed-off-by: Leonardo Neumann <leonardo@neumann.dev.br>
Signed-off-by: Georgy Yakovlev <gyakovlev@gentoo.org>
-rw-r--r-- | Cargo.lock | 259 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/audit.rs | 96 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/main.rs | 7 |
5 files changed, 368 insertions, 3 deletions
@@ -38,6 +38,12 @@ dependencies = [ ] [[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] name = "base-x" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -115,6 +121,7 @@ dependencies = [ "cargo_metadata", "itertools", "phf", + "rustsec", "serde", "structopt", "tera", @@ -156,6 +163,15 @@ dependencies = [ ] [[package]] +name = "cc" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +dependencies = [ + "jobserver", +] + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -183,6 +199,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" [[package]] +name = "crates-index" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad4af5c8dd9940a497ef4473e6e558b660a4a1b6e5ce2cb9d85454e2aaaf947" +dependencies = [ + "git2", + "glob", + "hex", + "home", + "memchr", + "semver 1.0.4", + "serde", + "serde_derive", + "serde_json", + "smartstring", +] + +[[package]] name = "crossbeam-utils" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -193,6 +227,15 @@ dependencies = [ ] [[package]] +name = "cvss" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829862dabeab142ae0efd558d42d8fd874659268ccd810809ac6f1ee6bfcbd3f" +dependencies = [ + "serde", +] + +[[package]] name = "digest" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -236,6 +279,12 @@ dependencies = [ ] [[package]] +name = "fs-err" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0" + +[[package]] name = "generic-array" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -256,6 +305,27 @@ dependencies = [ ] [[package]] +name = "git2" +version = "0.13.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659cd14835e75b64d9dba5b660463506763cf0aa6cb640aeeb0e98d841093490" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] name = "globset" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -298,6 +368,40 @@ dependencies = [ ] [[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058" +dependencies = [ + "humantime", + "serde", +] + +[[package]] name = "idna" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -342,6 +446,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -354,6 +467,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] +name = "libgit2-sys" +version = "0.12.22+1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c53ac117c44f7042ad8d8f5681378dfbc6010e49ec2c0d1f11dfedc7a4a1c3" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0186af0d8f171ae6b9c4c90ec51898bad5d08a2d5e470903a50d9ad8959cbee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -393,6 +546,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -486,6 +658,21 @@ dependencies = [ ] [[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "platforms" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989d43012e2ca1c4a02507c67282691a0a3207f9dc67cec596b43fe925b3d325" +dependencies = [ + "serde", +] + +[[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -606,6 +793,29 @@ dependencies = [ ] [[package]] +name = "rustsec" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c29c220a60ceaeedb2c5bf51826b3d3c5d77b2523693f0579c8a85dd03f11947" +dependencies = [ + "cargo-lock", + "crates-index", + "cvss", + "fs-err", + "git2", + "home", + "humantime", + "humantime-serde", + "platforms", + "semver 1.0.4", + "serde", + "smol_str", + "thiserror", + "toml", + "url", +] + +[[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -700,6 +910,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1" [[package]] +name = "smartstring" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa6a31c0c2b21327ce875f7e8952322acfcfd0c27569a6e18a647281352c9b" +dependencies = [ + "serde", + "static_assertions", +] + +[[package]] +name = "smol_str" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca0f7ce3a29234210f0f4f0b56f8be2e722488b95cb522077943212da3b32eb" + +[[package]] name = "standback" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -709,6 +935,12 @@ dependencies = [ ] [[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] name = "stdweb" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -824,6 +1056,26 @@ dependencies = [ ] [[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "thread_local" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -999,9 +1251,16 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -27,6 +27,7 @@ anyhow = "^1" cargo-lock = "^7.0" cargo_metadata = "^0.14" itertools = "^0.10" +rustsec = "0.24.2" structopt = "^0.3" serde = { version = "1.0", features = ["derive"] } time = "^0.2" diff --git a/src/audit.rs b/src/audit.rs new file mode 100644 index 0000000..8ea8e43 --- /dev/null +++ b/src/audit.rs @@ -0,0 +1,96 @@ +use std::env; +use std::path::Path; +use std::process::Command; + +use anyhow::{format_err, Context, Result}; +use rustsec::lockfile::Lockfile; +use rustsec::report::{Settings, VulnerabilityInfo}; +use rustsec::{Database, Report, Vulnerability}; + +fn generate_lockfile(workspace_root: &Path, manifest_path: Option<&Path>) -> Result<Lockfile> { + let lockfile = workspace_root.join("Cargo.lock"); + let mut command = Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())); + + if lockfile.exists() { + return Lockfile::load(lockfile).context("Failed to load lockfile"); + } + + command.arg("generate-lockfile"); + + if let Some(path) = manifest_path { + command.arg("--manifest-path"); + command.arg(path.as_os_str()); + } + + let status = command + .status() + .context("Failed to run `cargo generate-lockfile`")?; + + match status.code() { + Some(0) => Lockfile::load(lockfile).context("Failed to load lockfile"), + Some(code) => Err(format_err!( + "Non-zero status ({}) on `cargo generate-lockfile`", + code, + )), + None => Err(format_err!( + "Unexpected termination on `cargo generate-lockfile`", + )), + } +} + +pub fn audit_package(workspace_root: &Path, manifest_path: Option<&Path>) -> Result<()> { + let database = Database::fetch().context("Failed to fetch security advisory database")?; + let lockfile = generate_lockfile(workspace_root, manifest_path)?; + let settings = Settings::default(); + let report = Report::generate(&database, &lockfile, &settings); + + if report.vulnerabilities.found { + let VulnerabilityInfo { count, list, .. } = report.vulnerabilities; + + let mut message = match count { + 1 => format!("Found {} vulnerability:\n", count), + _ => format!("Found {} vulnerabilities:\n", count), + }; + + for Vulnerability { + package, + versions, + advisory, + .. + } in list + { + message.push('\n'); + message.push_str(&format!("Crate: {}\n", package.name)); + message.push_str(&format!("Version: {}\n", package.version.to_string())); + message.push_str(&format!("Title: {}\n", advisory.title)); + message.push_str(&format!("Date: {}\n", advisory.date.as_str())); + message.push_str(&format!("ID: {}\n", advisory.id)); + + if let Some(url) = advisory.id.url() { + message.push_str(&format!("URL: {}\n", url)); + } else if let Some(url) = &advisory.url { + message.push_str(&format!("URL: {}\n", url)); + } + + if versions.patched().is_empty() { + message.push_str("Solution: No solution available\n"); + } else { + let patched = versions + .patched() + .iter() + .map(ToString::to_string) + .collect::<Vec<_>>() + .as_slice() + .join(" or "); + + message.push_str(&format!("Solution: Upgrade to {}\n", patched)); + } + } + + message.push_str("\nPlease fix the issues or use \"--noaudit\" flag.\n"); + + return Err(format_err!(message)); + } + + Ok(()) +} @@ -8,6 +8,7 @@ * except according to those terms. */ +mod audit; mod license; mod metadata; @@ -18,10 +19,11 @@ use std::collections::BTreeSet; use std::fs::OpenOptions; use std::path::Path; +use audit::audit_package; use license::{normalize_license, split_spdx_license}; use metadata::EbuildConfig; -pub fn gen_ebuild_data(manifest_path: Option<&Path>) -> Result<EbuildConfig> { +pub fn gen_ebuild_data(manifest_path: Option<&Path>, audit: bool) -> Result<EbuildConfig> { let mut cmd = MetadataCommand::new(); cmd.features(CargoOpt::AllFeatures); @@ -44,6 +46,10 @@ pub fn gen_ebuild_data(manifest_path: Option<&Path>) -> Result<EbuildConfig> { .as_ref() .ok_or_else(|| format_err!("cargo metadata failed to resolve the root package"))?; + if audit { + audit_package(metadata.workspace_root.as_ref(), manifest_path)?; + } + let mut licenses = BTreeSet::new(); let mut crates = Vec::new(); let mut root_pkg = None; diff --git a/src/main.rs b/src/main.rs index 12dd0e0..1072094 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,9 +22,13 @@ struct Args { #[structopt(name = "PATH", long = "manifest-path", parse(from_os_str))] /// Path to Cargo.toml. manifest_path: Option<PathBuf>, + #[structopt(name = "TEMPLATE", long = "template-path", short)] /// Non-standard template template_path: Option<PathBuf>, + + #[structopt(long)] + noaudit: bool, } #[derive(StructOpt, Debug)] @@ -45,8 +49,7 @@ fn main() -> Result<()> { let Opt::Ebuild(opt) = Opt::from_args(); // compute the data from the package that the build needs - let ebuild_data = gen_ebuild_data(opt.manifest_path.as_deref())?; - + let ebuild_data = gen_ebuild_data(opt.manifest_path.as_deref(), !opt.noaudit)?; let ebuild_path = format!("{}-{}.ebuild", ebuild_data.name, ebuild_data.version); write_ebuild(ebuild_data, ebuild_path.as_ref(), opt.template_path.as_deref())?; |