Skip to content

Commit

Permalink
feat: Add NVD collector
Browse files Browse the repository at this point in the history
chore: Add NVD and Snyk to compose.
  • Loading branch information
bobmcwhirter committed Sep 13, 2023
1 parent 89b7f27 commit 6f49312
Show file tree
Hide file tree
Showing 15 changed files with 703 additions and 0 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ members = [
"collector/client",
"collector/osv",
"collector/snyk",
"collector/nvd",
"v11y/api",
"v11y/client",
"v11y/v11y",
Expand Down Expand Up @@ -77,6 +78,7 @@ default-members = [
"collector/client",
"collector/osv",
"collector/snyk",
"collector/nvd",
"v11y/api",
"v11y/client",
"v11y/v11y",
Expand Down
1 change: 1 addition & 0 deletions collector/collector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
collector-osv = { path = "../osv" }
collector-snyk = { path = "../snyk" }
collector-nvd = { path = "../nvd" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
tokio = { version = "1.0", features = ["full"] }
Expand Down
2 changes: 2 additions & 0 deletions collector/collector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ use std::process::ExitCode;
pub enum Command {
Osv(collector_osv::Run),
Snyk(collector_snyk::Run),
Nvd(collector_nvd::Run),
}

impl Command {
pub async fn run(self) -> anyhow::Result<ExitCode> {
match self {
Self::Osv(run) => run.run().await,
Self::Snyk(run) => run.run().await,
Self::Nvd(run) => run.run().await,
}
}
}
48 changes: 48 additions & 0 deletions collector/nvd/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[package]
name = "collector-nvd"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "4"
trustification-auth = { path = "../../auth" }
trustification-infrastructure = { path = "../../infrastructure" }
collectorist-api = { path = "../../collectorist/api"}
collectorist-client = { path = "../../collectorist/client"}
collector-client = { path = "../client" }
v11y-client = { path = "../../v11y/client" }
clap = { version = "4", features = ["derive"] }
anyhow = "1"
derive_more = "0.99"
futures = "0.3"
log = "0.4"
utoipa = { version = "3", features = ["actix_extras"] }
utoipa-swagger-ui = { version = "3", features = ["actix-web"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
tokio = { version = "1.0", features = ["full"] }
guac = "0.1"
packageurl = "0.3.0"
#guac = { path = "../../../guac-rs/lib" }
#sqlx = { version = "0.7.0", features = [ "runtime-tokio", "sqlite", "chrono"] }
reqwest = "0.11.18"
#osv = { version = "0.1.3" }
chrono = "0.4.26"
#typify = { version = "0.0.13", path = "../../../typify/typify" }
#schemafy = "0.6.0"
#chrono = "0.4.26"
#humantime = "2"
#humantime-serde = "1.1.1"


[dev-dependencies]
env_logger = "0.10"
test-with = { version = "0.10", features = ["runtime"] }
libtest-with = { version = "0.6.1-0", features = ["net", "resource", "user", "executable"] }


[build-dependencies]
#schemafy_lib = "0.6.0"
#tonic-build = "0.9.2"
8 changes: 8 additions & 0 deletions collector/nvd/src/client/cvss2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Cvss2Data {
pub base_score: f32,
pub vector_string: String,
}
8 changes: 8 additions & 0 deletions collector/nvd/src/client/cvss30.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Cvss30Data {
pub base_score: f32,
pub vector_string: String,
}
8 changes: 8 additions & 0 deletions collector/nvd/src/client/cvss31.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Cvss31Data {
pub base_score: f32,
pub vector_string: String,
}
82 changes: 82 additions & 0 deletions collector/nvd/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use reqwest::Url;

use crate::client::schema::{QueryResponse, Vulnerability};

pub mod cvss2;
pub mod cvss30;
pub mod cvss31;
pub mod schema;

pub struct NvdUrl(&'static str);

impl NvdUrl {
const fn new(base: &'static str) -> Self {
Self(base)
}

fn url(&self) -> Url {
Url::parse(self.0).unwrap()
}
}

const NVD_URL: NvdUrl = NvdUrl::new("https://services.nvd.nist.gov/rest/json/cves/2.0");

pub struct NvdClient {
api_key: String,
client: reqwest::Client,
}

impl NvdClient {
pub fn new(api_token: &str) -> Self {
Self {
api_key: api_token.to_string(),
client: reqwest::Client::new(),
}
}

pub async fn get_cve(&self, cve_id: &str) -> Result<Option<Vulnerability>, anyhow::Error> {
let response = self
.client
.get(NVD_URL.url())
.header("apiKey", &self.api_key)
.query(&[("cveId", cve_id)])
.send()
.await?;

if response.status() != 200 {
return Ok(None);
}

let response: QueryResponse = response.json().await?;

Ok(Some(response.vulnerabilities[0].clone()))
}
}

#[cfg(test)]
mod test {
use crate::client::NvdClient;

pub fn client() -> Result<NvdClient, anyhow::Error> {
let api_key = std::env::var("NVD_API_KEY")?;

Ok(NvdClient::new(&api_key))
}

#[test_with::env(NVD_API_KEY)]
#[tokio::test]
async fn get_valid() -> Result<(), anyhow::Error> {
let vuln = client()?.get_cve("CVE-2019-1010218").await?;

assert!(vuln.is_some());
Ok(())
}

#[test_with::env(NVD_API_KEY)]
#[tokio::test]
async fn get_invalid() -> Result<(), anyhow::Error> {
let vuln = client()?.get_cve("CVE-NOT-2019-1010218").await?;
assert!(vuln.is_none());
Ok(())
}
}
82 changes: 82 additions & 0 deletions collector/nvd/src/client/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::client::cvss2::Cvss2Data;
use crate::client::cvss30::Cvss30Data;
use crate::client::cvss31::Cvss31Data;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct QueryResponse {
pub vulnerabilities: Vec<Vulnerability>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Vulnerability {
pub cve: Cve,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Cve {
pub id: String,
pub source_identifier: Option<String>,
pub published: NaiveDateTime,
pub last_modified: NaiveDateTime,
pub evaluator_comment: Option<String>,
pub evaluator_solution: Option<String>,
pub evaluator_impact: Option<String>,
pub descriptions: Vec<LangString>,
pub references: Vec<Reference>,
pub metrics: Option<Metrics>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LangString {
pub lang: String,
pub value: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Reference {
pub url: String,
pub source: Option<String>,
pub tags: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Metrics {
#[serde(default = "Vec::default")]
pub cvss_metric_v31: Vec<CvssV31>,
#[serde(default = "Vec::default")]
pub cvss_metric_v30: Vec<CvssV30>,
#[serde(default = "Vec::default")]
pub cvss_metric_v2: Vec<CvssV2>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CvssV31 {
pub source: String,
pub r#type: String,
pub cvss_data: Cvss31Data,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CvssV30 {
pub source: String,
pub r#type: String,
pub cvss_data: Cvss30Data,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CvssV2 {
pub source: String,
pub r#type: String,
pub cvss_data: Cvss2Data,
}

0 comments on commit 6f49312

Please sign in to comment.