From 825c86798a667aa0a36e19d76fb1097003843e05 Mon Sep 17 00:00:00 2001 From: mrl5 <31549762+mrl5@users.noreply.github.com> Date: Tue, 13 Sep 2022 20:30:12 +0200 Subject: [PATCH] feat(scan): include Funtoo bugtracker tickets for detected CVEs [#30] as a funtoo linux user I'd like to know if there is already a jira ticket for cve reported in scan result --- README.md | 2 ++ crates/cli/src/command.rs | 5 +++ crates/cli/src/command/scan.rs | 32 +++++++++++++++++-- crates/cli/src/command/tracker.rs | 2 +- crates/security-advisories/src/cve_summary.rs | 2 ++ crates/security-advisories/src/service.rs | 19 ++++++++++- .../security-advisories/src/service/funtoo.rs | 24 ++++++++++++-- 7 files changed, 79 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8df9a6a..41bf118 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Discover CVEs for software. - **Use case 5)** as a [Funtoo Linux] maintainer I want to scan all meta-repo for CVEs - **Use case 6)** as a [Funtoo Linux] user I want to list bug tracker security vulnerability tickets that are not fixed +- **Use case 7)** as a [Funtoo Linux] user I want to know if there is already a + ticket for CVE detected by `vulner` ## API keys diff --git a/crates/cli/src/command.rs b/crates/cli/src/command.rs index 0117a54..ce9787c 100644 --- a/crates/cli/src/command.rs +++ b/crates/cli/src/command.rs @@ -51,6 +51,7 @@ pub async fn execute(cmd: Command) -> Result<(), Box> { pkg_dir, recursive, api_keys: _, + no_bugtracker, } => { scan::execute( cpe_feed.feed_dir, @@ -58,6 +59,7 @@ pub async fn execute(cmd: Command) -> Result<(), Box> { pkg_dir, recursive, cfg.api_keys, + no_bugtracker, ) .await } @@ -125,6 +127,9 @@ pub enum Command { #[structopt(flatten)] api_keys: conf::ApiKeys, + + #[structopt(short, long, help = "Don't query distro bugtracker")] + no_bugtracker: bool, }, #[structopt( diff --git a/crates/cli/src/command/scan.rs b/crates/cli/src/command/scan.rs index a2f1538..2f4b431 100644 --- a/crates/cli/src/command/scan.rs +++ b/crates/cli/src/command/scan.rs @@ -15,7 +15,7 @@ use reqwest::Client; use security_advisories::cve_summary::CveSummary; use security_advisories::http::get_client; use security_advisories::service::{ - fetch_cves_by_cpe, fetch_known_exploited_cves, get_cves_summary, + fetch_cves_by_cpe, fetch_known_exploited_cves, get_cves_summary, get_distro_tickets_by_cve, }; use std::collections::{HashMap, HashSet}; use std::error::Error; @@ -31,6 +31,7 @@ pub async fn execute( pkg_dir: Option, recursive: bool, api_keys: ApiKeys, + no_bugtracker: bool, ) -> Result<(), Box> { // todo: progress bar let now = OffsetDateTime::now_utc(); @@ -60,6 +61,7 @@ pub async fn execute( &feed_dir, &known_exploited_cves, &api_keys, + no_bugtracker, ) .await?; } else { @@ -74,6 +76,7 @@ pub async fn execute( &feed_dir, &known_exploited_cves, &api_keys, + no_bugtracker, ) .await?; } @@ -90,6 +93,7 @@ async fn scan( feed_dir: &Path, known_exploited_cves: &[String], api_keys: &ApiKeys, + no_bugtracker: bool, ) -> Result<(), Box> { log::info!("listing all catpkgs ..."); let catpkgs = os.get_all_catpkgs()?; @@ -114,6 +118,12 @@ async fn scan( .par_iter() .map(|(pkg, re_pattern)| match_cpes(&feed_buffer, pkg, re_pattern)) .collect_into_vec(&mut result_buffer); + + let mut os_adapter = None; + if !no_bugtracker { + os_adapter = Some(os); + } + handle_pkgs( client, &cwd, @@ -121,6 +131,7 @@ async fn scan( &result_buffer, known_exploited_cves, api_keys, + os_adapter, ) .await?; } @@ -159,6 +170,7 @@ async fn handle_pkgs( pkgs: &[HashMap<&Package, HashSet>], known_exploited_cves: &[String], api_keys: &ApiKeys, + os: Option<&'_ dyn OsAdapter>, ) -> Result<(), Box> { let mut any_cpes = false; for items in pkgs { @@ -183,6 +195,7 @@ async fn handle_pkgs( matches, known_exploited_cves, api_keys, + os, ) .await?; } @@ -205,6 +218,7 @@ async fn handle_cves( matches: &HashSet, known_exploited_cves: &[String], api_keys: &ApiKeys, + os: Option<&'_ dyn OsAdapter>, ) -> Result<(), Box> { let mut already_notified = false; let mut cves: HashSet = HashSet::new(); @@ -213,7 +227,10 @@ async fn handle_cves( match fetch_cves_by_cpe(client, cpe, api_keys).await { Ok(res) => match get_cves_summary(&res, Some(known_exploited_cves)) { Ok(summary) => { - for cve in summary { + for mut cve in summary { + if let Some(os_adapter) = os { + cve.tickets = get_distro_tickets(client, os_adapter, &cve.id).await; + } cves.insert(cve); } } @@ -260,3 +277,14 @@ fn write_report( Ok(()) } + +async fn get_distro_tickets( + client: &Client, + os: &'_ dyn OsAdapter, + cve_id: &String, +) -> Option> { + match get_distro_tickets_by_cve(client, os, cve_id.to_owned()).await { + Ok(tickets) => Some(tickets), + Err(_) => None, + } +} diff --git a/crates/cli/src/command/tracker.rs b/crates/cli/src/command/tracker.rs index 58e209f..2625ffc 100644 --- a/crates/cli/src/command/tracker.rs +++ b/crates/cli/src/command/tracker.rs @@ -14,7 +14,7 @@ use std::error::Error; pub async fn execute() -> Result<(), Box> { let os = get_adapter(None, None)?; let client = get_client()?; - let tracker_summary = get_distro_tracker_summary(&client, os).await?; + let tracker_summary = get_distro_tracker_summary(&client, &*os).await?; println!("{}", to_string(&tracker_summary)?); Ok(()) diff --git a/crates/security-advisories/src/cve_summary.rs b/crates/security-advisories/src/cve_summary.rs index f742c72..49be0d1 100644 --- a/crates/security-advisories/src/cve_summary.rs +++ b/crates/security-advisories/src/cve_summary.rs @@ -14,6 +14,7 @@ use std::hash::{Hash, Hasher}; pub struct CveSummary { pub id: String, pub is_known_exploited_vuln: Option, + pub tickets: Option>, pub description: String, pub urls: Vec, } @@ -23,6 +24,7 @@ impl CveSummary { Self { id, is_known_exploited_vuln: None, + tickets: None, description, urls, } diff --git a/crates/security-advisories/src/service.rs b/crates/security-advisories/src/service.rs index e30974e..30ee0ca 100644 --- a/crates/security-advisories/src/service.rs +++ b/crates/security-advisories/src/service.rs @@ -78,7 +78,7 @@ pub async fn fetch_known_exploited_cves(client: &Client) -> Result, pub async fn get_distro_tracker_summary( client: &Client, - os: Box, + os: &'_ dyn OsAdapter, ) -> Result, Box> { if *os.get_os() != Os::GnuLinux { return Err(Box::new(IOError::from(ErrorKind::Unsupported))); @@ -89,3 +89,20 @@ pub async fn get_distro_tracker_summary( _ => Err(Box::new(IOError::from(ErrorKind::Unsupported))), } } + +pub async fn get_distro_tickets_by_cve( + client: &Client, + os: &'_ dyn OsAdapter, + cve_id: String, +) -> Result, Box> { + if *os.get_os() != Os::GnuLinux { + return Err(Box::new(IOError::from(ErrorKind::Unsupported))); + } + + match os.get_os_flavor() { + Some(OsFlavor::LinuxDistro(&LinuxDistro::Funtoo)) => { + funtoo::get_tickets_by_cve(client, cve_id).await + } + _ => Err(Box::new(IOError::from(ErrorKind::Unsupported))), + } +} diff --git a/crates/security-advisories/src/service/funtoo.rs b/crates/security-advisories/src/service/funtoo.rs index a28b331..079a2d9 100644 --- a/crates/security-advisories/src/service/funtoo.rs +++ b/crates/security-advisories/src/service/funtoo.rs @@ -18,7 +18,8 @@ const VULN_BUG_TYPE: &str = "10200"; pub async fn get_vuln_tracker( client: &Client, ) -> Result, Box> { - let resp = fetch_vuln_tracker(client).await?; + let query = format!("issuetype = {} AND statuscategory != Done", VULN_BUG_TYPE); + let resp = search_vuln_tracker(client, query).await?; let mut summary = vec![]; if let Some(issues) = resp["issues"].as_array() { @@ -38,8 +39,25 @@ pub async fn get_vuln_tracker( Ok(summary) } -async fn fetch_vuln_tracker(client: &Client) -> Result> { - let query = format!("issuetype = {} AND statuscategory != Done", VULN_BUG_TYPE); +pub async fn get_tickets_by_cve( + client: &Client, + cve: String, +) -> Result, Box> { + let query = format!("issuetype = {} AND text ~ {}", VULN_BUG_TYPE, cve); + let resp = search_vuln_tracker(client, query).await?; + let mut tickets = vec![]; + + if let Some(issues) = resp["issues"].as_array() { + for issue in issues { + let ticket = issue["key"].as_str().unwrap_or(""); + tickets.push(ticket.to_owned()); + } + } + + Ok(tickets) +} + +async fn search_vuln_tracker(client: &Client, query: String) -> Result> { let url = format!( "{}/{}/search?fields=key,summary&jql={}", BUGS_URL, API_PATH, query