Upgrade to rustsec crate v0.13.0-alpha1; add linter

Upgrades the `rustsec` crate to the latest alpha release and uses the
new `rustsec::advisory::Linter` functionality to lint advisories
currently in the database.

Several of them are using invalid keys and need to be updated.
This commit is contained in:
Tony Arcieri
2019-09-09 11:39:11 -07:00
parent a1cd9fc432
commit 74ebe212dc
2 changed files with 164 additions and 79 deletions

View File

@@ -10,6 +10,7 @@ publish = false
name = "rustsec-advisory-db"
[dependencies]
gumdrop = "0.6"
rustsec = "0.12"
crates_io_api = "0.5"
gumdrop = "0.6"
rustsec = "0.13.0-alpha1"
termcolor = "1"

View File

@@ -1,120 +1,204 @@
//! RustSec Advisory DB Linter
#![allow(clippy::never_loop)]
use gumdrop::Options;
use rustsec::{AdvisoryDatabase, Repository};
use std::{env, process::exit};
use rustsec::package;
use std::{env, io::Write, path::PathBuf, process::exit};
use termcolor::{
Color::{Green, Red},
ColorChoice, ColorSpec, StandardStream, WriteColor,
};
const MIN_EXPECTED_ADVISORIES: usize = 5;
/// Minimum number of advisories we expect in the database
const MIN_EXPECTED_ADVISORIES: usize = 10;
/// Subcommands
#[derive(Debug, Options)]
enum Opts {
#[options(help = "show help for a command")]
Help(HelpOpts),
macro_rules! writeln_color {
($stream:expr, $color:path, $fmt:expr, $msg:expr) => {
let mut color = ColorSpec::new();
color.bold();
color.set_fg(Some($color));
$stream.set_color(&color).unwrap();
#[options(help = "check the advisory DB is well-formed")]
Check(CheckOpts),
writeln!($stream, $fmt, $msg).unwrap();
$stream.reset().unwrap();
};
}
/// Options for the `help` command
#[derive(Debug, Default, Options)]
struct HelpOpts {
#[options(free)]
commands: Vec<String>,
macro_rules! writeln_success {
($stream:expr, $msg:expr) => {
writeln_color!($stream, Green, "✔ {}", $msg);
};
($stream:expr, $fmt:expr, $($arg:tt)+) => {
writeln_success!($stream, format!($fmt, $($arg)+));
}
}
/// Options for the `check` command
#[derive(Debug, Default, Options)]
struct CheckOpts {}
macro_rules! writeln_error {
($stream:expr, $msg:expr) => {
writeln_color!($stream, Red, "✘ {}", $msg);
};
($stream:expr, $fmt:expr, $($arg:tt)+) => {
writeln_error!($stream, format!($fmt, $($arg)+));
}
}
fn main() {
let args: Vec<_> = env::args().collect();
let opts = Opts::parse_args_default(&args[1..]).unwrap_or_else(|e| {
let opts = Commands::parse_args_default(&args[1..]).unwrap_or_else(|e| {
match e.to_string().as_ref() {
// Show usage if no command name is given or if "help" is given
"missing command name" => help(&[]),
"missing command name" => help(),
string => eprintln!("{}: {}", args[0], string),
}
exit(2);
exit(1);
});
match opts {
Opts::Help(opts) => help(&opts.commands),
Opts::Check(_) => check(),
Commands::Help(_) => help(),
Commands::Check(check) => check.run(),
}
}
exit(0);
/// Subcommands
#[derive(Debug, Options)]
enum Commands {
#[options(help = "show help for a command")]
Help(HelpCommand),
#[options(help = "check the advisory DB is well-formed")]
Check(CheckCommand),
}
/// Options for the `help` command
#[derive(Debug, Default, Options)]
struct HelpCommand {
#[options(free)]
commands: Vec<String>,
}
/// Print help message
fn help(_commands: &[String]) {
fn help() {
println!("Usage: {} [COMMAND] [OPTIONS]", env::args().next().unwrap());
println!();
println!("Available commands:");
println!();
println!("{}", Opts::command_list().unwrap());
println!("{}", Commands::command_list().unwrap());
println!();
}
fn check() {
let repo = Repository::open(".").unwrap();
/// Options for the `check` command
#[derive(Debug, Default, Options)]
struct CheckCommand {}
// Ensure Advisories.toml parses
let db = AdvisoryDatabase::from_repository(&repo).unwrap();
let advisories = db.advisories();
impl CheckCommand {
fn run(&self) {
let mut stdout = StandardStream::stdout(ColorChoice::Auto);
let repo = rustsec::Repository::open(".").unwrap();
// Ensure we're parsing some advisories
if advisories.len() > MIN_EXPECTED_ADVISORIES {
println!(
"*** Check succeeded! Successfully parsed {} advisories.",
advisories.len()
);
} else {
panic!(
"Missing advisories! Expected at least {}, but got {}",
MIN_EXPECTED_ADVISORIES,
advisories.len()
);
// Ensure Advisories.toml parses
let db = rustsec::Database::load(&repo).unwrap();
let advisories = db.iter();
// Ensure we're parsing some advisories
if advisories.len() > MIN_EXPECTED_ADVISORIES {
writeln_success!(
&mut stdout,
"Successfully parsed {} advisories",
advisories.len()
);
} else {
writeln_error!(
&mut stdout,
"Missing advisories! Expected at least {}, but got {}",
MIN_EXPECTED_ADVISORIES,
advisories.len()
);
exit(1);
}
let cratesio_client = crates_io_api::SyncClient::new();
let mut invalid_advisories = 0;
for advisory in advisories {
if !self.check_advisory(&mut stdout, &cratesio_client, advisory) {
invalid_advisories += 1;
}
}
if invalid_advisories == 0 {
writeln_success!(&mut stdout, "All advisories are well-formed");
} else {
writeln_error!(
&mut stdout,
"{} advisories contain errors!",
invalid_advisories
);
exit(1);
}
}
let cratesio_client = crates_io_api::SyncClient::new();
for advisory in advisories {
check_advisory(&cratesio_client, advisory);
}
println!("*** Check succeeded! All advisories refer to valid crates.");
}
fn check_advisory(cratesio_client: &crates_io_api::SyncClient, advisory: &rustsec::Advisory) {
let response = cratesio_client
.get_crate(advisory.package.as_str())
.unwrap_or_else(|_| {
panic!(
"Failed to get package from crates.io: {}",
advisory.package.as_str()
)
});
if response.crate_data.name != advisory.package.as_str() {
panic!(
"crates.io package name does not match package name in advisory for {}",
advisory.package.as_str()
);
}
// Check that each path in `affected_paths` starts with the crate name
if let Some(ref version_req_paths) = advisory.affected_paths {
for (_, paths) in version_req_paths.iter() {
for path in paths {
if path.crate_name() != response.crate_data.name {
panic!(
"{}: affected_path does not begin with crate name: {}",
response.crate_data.name,
path.crate_name()
)
fn check_advisory(
&self,
stdout: &mut StandardStream,
cratesio_client: &crates_io_api::SyncClient,
advisory: &rustsec::Advisory,
) -> bool {
if advisory.metadata.collection == Some(package::Collection::Crates) {
match cratesio_client.get_crate(advisory.metadata.package.as_str()) {
Ok(response) => {
if response.crate_data.name != advisory.metadata.package.as_str() {
writeln_error!(
stdout,
"crates.io package name does not match package name in advisory for {}",
advisory.metadata.package.as_str()
);
return false;
}
}
Err(err) => {
writeln_error!(
stdout,
"Failed to get package `{}` from crates.io: {}",
advisory.metadata.package.as_str(),
err
);
return false;
}
}
}
let mut advisory_path = PathBuf::from(".")
.join(advisory.metadata.collection.as_ref().unwrap().to_string())
.join(advisory.metadata.package.as_str())
.join(advisory.metadata.id.as_str());
advisory_path.set_extension("toml");
let lint = rustsec::advisory::Linter::lint_file(&advisory_path).unwrap();
if lint.errors().is_empty() {
writeln_success!(
stdout,
"{} successfully passed lint",
advisory_path.display()
);
true
} else {
writeln_error!(
stdout,
"{} contained the following lint errors:",
advisory_path.display()
);
for error in lint.errors() {
writeln!(stdout, " - {}", error).unwrap();
}
false
}
}
}