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 /src | |
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>
Diffstat (limited to 'src')
-rw-r--r-- | src/audit.rs | 96 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/main.rs | 7 |
3 files changed, 108 insertions, 3 deletions
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())?; |