aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLeonardo Neumann <leonardo@neumann.dev.br>2021-08-14 18:05:37 -0300
committerGeorgy Yakovlev <gyakovlev@gentoo.org>2021-08-26 00:40:36 -0700
commit1f8c7eb03ace33caba40822651c822d140f2840c (patch)
treec90a99f3208f03956d1ef9ffc1c6010d1ebb5c53 /src
parentUse path references instead of owned counterparts (diff)
downloadcargo-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.rs96
-rw-r--r--src/lib.rs8
-rw-r--r--src/main.rs7
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(())
+}
diff --git a/src/lib.rs b/src/lib.rs
index 6b2d2d3..f478bc0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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())?;