mirror of
https://github.com/OMGeeky/advisory-db.git
synced 2026-01-01 09:10:25 +01:00
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:
@@ -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"
|
||||
|
||||
238
src/main.rs
238
src/main.rs
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user