Skip to content

Commit

Permalink
feat(scan): include Funtoo bugtracker tickets for detected CVEs [#30]
Browse files Browse the repository at this point in the history
as a funtoo linux user I'd like to know if there is already a jira
ticket for cve reported in scan result
  • Loading branch information
mrl5 committed Sep 13, 2022
1 parent 41a6303 commit 825c867
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 7 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions crates/cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ pub async fn execute(cmd: Command) -> Result<(), Box<dyn Error>> {
pkg_dir,
recursive,
api_keys: _,
no_bugtracker,
} => {
scan::execute(
cpe_feed.feed_dir,
out_dir.unwrap_or(cfg.scan_results_dir),
pkg_dir,
recursive,
cfg.api_keys,
no_bugtracker,
)
.await
}
Expand Down Expand Up @@ -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(
Expand Down
32 changes: 30 additions & 2 deletions crates/cli/src/command/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +31,7 @@ pub async fn execute(
pkg_dir: Option<PathBuf>,
recursive: bool,
api_keys: ApiKeys,
no_bugtracker: bool,
) -> Result<(), Box<dyn Error>> {
// todo: progress bar
let now = OffsetDateTime::now_utc();
Expand Down Expand Up @@ -60,6 +61,7 @@ pub async fn execute(
&feed_dir,
&known_exploited_cves,
&api_keys,
no_bugtracker,
)
.await?;
} else {
Expand All @@ -74,6 +76,7 @@ pub async fn execute(
&feed_dir,
&known_exploited_cves,
&api_keys,
no_bugtracker,
)
.await?;
}
Expand All @@ -90,6 +93,7 @@ async fn scan(
feed_dir: &Path,
known_exploited_cves: &[String],
api_keys: &ApiKeys,
no_bugtracker: bool,
) -> Result<(), Box<dyn Error>> {
log::info!("listing all catpkgs ...");
let catpkgs = os.get_all_catpkgs()?;
Expand All @@ -114,13 +118,20 @@ 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,
&ctg,
&result_buffer,
known_exploited_cves,
api_keys,
os_adapter,
)
.await?;
}
Expand Down Expand Up @@ -159,6 +170,7 @@ async fn handle_pkgs(
pkgs: &[HashMap<&Package, HashSet<String>>],
known_exploited_cves: &[String],
api_keys: &ApiKeys,
os: Option<&'_ dyn OsAdapter>,
) -> Result<(), Box<dyn Error>> {
let mut any_cpes = false;
for items in pkgs {
Expand All @@ -183,6 +195,7 @@ async fn handle_pkgs(
matches,
known_exploited_cves,
api_keys,
os,
)
.await?;
}
Expand All @@ -205,6 +218,7 @@ async fn handle_cves(
matches: &HashSet<String>,
known_exploited_cves: &[String],
api_keys: &ApiKeys,
os: Option<&'_ dyn OsAdapter>,
) -> Result<(), Box<dyn Error>> {
let mut already_notified = false;
let mut cves: HashSet<CveSummary> = HashSet::new();
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -260,3 +277,14 @@ fn write_report(

Ok(())
}

async fn get_distro_tickets(
client: &Client,
os: &'_ dyn OsAdapter,
cve_id: &String,
) -> Option<Vec<String>> {
match get_distro_tickets_by_cve(client, os, cve_id.to_owned()).await {
Ok(tickets) => Some(tickets),
Err(_) => None,
}
}
2 changes: 1 addition & 1 deletion crates/cli/src/command/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::error::Error;
pub async fn execute() -> Result<(), Box<dyn Error>> {
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(())
Expand Down
2 changes: 2 additions & 0 deletions crates/security-advisories/src/cve_summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::hash::{Hash, Hasher};
pub struct CveSummary {
pub id: String,
pub is_known_exploited_vuln: Option<bool>,
pub tickets: Option<Vec<String>>,
pub description: String,
pub urls: Vec<String>,
}
Expand All @@ -23,6 +24,7 @@ impl CveSummary {
Self {
id,
is_known_exploited_vuln: None,
tickets: None,
description,
urls,
}
Expand Down
19 changes: 18 additions & 1 deletion crates/security-advisories/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub async fn fetch_known_exploited_cves(client: &Client) -> Result<Vec<String>,

pub async fn get_distro_tracker_summary(
client: &Client,
os: Box<dyn OsAdapter>,
os: &'_ dyn OsAdapter,
) -> Result<Vec<DistroTrackerSummary>, Box<dyn Error>> {
if *os.get_os() != Os::GnuLinux {
return Err(Box::new(IOError::from(ErrorKind::Unsupported)));
Expand All @@ -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<Vec<String>, Box<dyn Error>> {
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))),
}
}
24 changes: 21 additions & 3 deletions crates/security-advisories/src/service/funtoo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const VULN_BUG_TYPE: &str = "10200";
pub async fn get_vuln_tracker(
client: &Client,
) -> Result<Vec<DistroTrackerSummary>, Box<dyn Error>> {
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() {
Expand All @@ -38,8 +39,25 @@ pub async fn get_vuln_tracker(
Ok(summary)
}

async fn fetch_vuln_tracker(client: &Client) -> Result<Value, Box<dyn Error>> {
let query = format!("issuetype = {} AND statuscategory != Done", VULN_BUG_TYPE);
pub async fn get_tickets_by_cve(
client: &Client,
cve: String,
) -> Result<Vec<String>, Box<dyn Error>> {
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<Value, Box<dyn Error>> {
let url = format!(
"{}/{}/search?fields=key,summary&jql={}",
BUGS_URL, API_PATH, query
Expand Down

0 comments on commit 825c867

Please sign in to comment.