From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id 64F071580B9 for ; Thu, 26 Aug 2021 07:51:45 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 576ABE0903; Thu, 26 Aug 2021 07:51:44 +0000 (UTC) Received: from smtp.gentoo.org (dev.gentoo.org [IPv6:2001:470:ea4a:1:5054:ff:fec7:86e4]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 35E2DE0903 for ; Thu, 26 Aug 2021 07:51:44 +0000 (UTC) Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTPS id D6FB0342BC7 for ; Thu, 26 Aug 2021 07:51:42 +0000 (UTC) Received: from localhost.localdomain (localhost [IPv6:::1]) by oystercatcher.gentoo.org (Postfix) with ESMTP id 2C4178C8 for ; Thu, 26 Aug 2021 07:51:41 +0000 (UTC) From: "Georgy Yakovlev" To: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: 8bit Content-type: text/plain; charset=UTF-8 Reply-To: gentoo-dev@lists.gentoo.org, "Georgy Yakovlev" Message-ID: <1629963636.1f8c7eb03ace33caba40822651c822d140f2840c.gyakovlev@gentoo> Subject: [gentoo-commits] proj/cargo-ebuild:master commit in: /, src/ X-VCS-Repository: proj/cargo-ebuild X-VCS-Files: Cargo.lock Cargo.toml src/audit.rs src/lib.rs src/main.rs X-VCS-Directories: / src/ X-VCS-Committer: gyakovlev X-VCS-Committer-Name: Georgy Yakovlev X-VCS-Revision: 1f8c7eb03ace33caba40822651c822d140f2840c X-VCS-Branch: master Date: Thu, 26 Aug 2021 07:51:41 +0000 (UTC) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply X-Archives-Salt: 140dd887-854c-4b48-baed-2ca04049d71c X-Archives-Hash: be4d2970c88ef3c1d09e4e5822130463 commit: 1f8c7eb03ace33caba40822651c822d140f2840c Author: Leonardo Neumann neumann dev br> AuthorDate: Sat Aug 14 21:05:37 2021 +0000 Commit: Georgy Yakovlev gentoo org> CommitDate: Thu Aug 26 07:40:36 2021 +0000 URL: https://gitweb.gentoo.org/proj/cargo-ebuild.git/commit/?id=1f8c7eb0 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 neumann.dev.br> Signed-off-by: Georgy Yakovlev gentoo.org> Cargo.lock | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/audit.rs | 96 ++++++++++++++++++++++ src/lib.rs | 8 +- src/main.rs | 7 +- 5 files changed, 368 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 431eb9d..06293a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,12 @@ dependencies = [ "winapi", ] +[[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" @@ -115,6 +121,7 @@ dependencies = [ "cargo_metadata", "itertools", "phf", + "rustsec", "serde", "structopt", "tera", @@ -155,6 +162,15 @@ dependencies = [ "serde_json", ] +[[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" @@ -182,6 +198,24 @@ version = "0.4.8" 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" @@ -192,6 +226,15 @@ dependencies = [ "lazy_static", ] +[[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" @@ -235,6 +278,12 @@ dependencies = [ "percent-encoding", ] +[[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" @@ -255,6 +304,27 @@ dependencies = [ "wasi", ] +[[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" @@ -297,6 +367,40 @@ dependencies = [ "libc", ] +[[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" @@ -341,6 +445,15 @@ version = "0.4.8" 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" @@ -353,6 +466,46 @@ version = "0.2.101" 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" @@ -392,6 +545,25 @@ version = "0.2.3" 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" @@ -485,6 +657,21 @@ dependencies = [ "siphasher", ] +[[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" @@ -605,6 +792,29 @@ dependencies = [ "semver 0.9.0", ] +[[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" @@ -699,6 +909,22 @@ version = "0.3.6" 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" @@ -708,6 +934,12 @@ dependencies = [ "version_check", ] +[[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" @@ -823,6 +1055,26 @@ dependencies = [ "unicode-width", ] +[[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" @@ -999,8 +1251,15 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index ef4be19..84e7883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 { + 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::>() + .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 { +pub fn gen_ebuild_data(manifest_path: Option<&Path>, audit: bool) -> Result { let mut cmd = MetadataCommand::new(); cmd.features(CargoOpt::AllFeatures); @@ -44,6 +46,10 @@ pub fn gen_ebuild_data(manifest_path: Option<&Path>) -> Result { .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, + #[structopt(name = "TEMPLATE", long = "template-path", short)] /// Non-standard template template_path: Option, + + #[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())?;