Skip to content

Commit

Permalink
Continue the -api/-graph more completely.
Browse files Browse the repository at this point in the history
Ingest a CVE record as a vuln and an advisory.
  • Loading branch information
Bob McWhirter committed Mar 14, 2024
1 parent c39386a commit e1cedcc
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 41 deletions.
12 changes: 8 additions & 4 deletions graph/src/graph/vulnerability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl Graph {
identifier: &str,
tx: Transactional<'_>,
) -> Result<VulnerabilityContext, Error> {
if let Some(found) = self.get_cve(identifier, tx).await? {
if let Some(found) = self.get_vulnerability(identifier, tx).await? {
Ok(found)
} else {
let entity = vulnerability::ActiveModel {
Expand All @@ -31,7 +31,7 @@ impl Graph {
}
}

pub async fn get_cve(
pub async fn get_vulnerability(
&self,
identifier: &str,
tx: Transactional<'_>,
Expand Down Expand Up @@ -105,7 +105,9 @@ mod tests {
assert_eq!(cve1.cve.id, cve2.cve.id);
assert_ne!(cve1.cve.id, cve3.cve.id);

let not_found = system.get_cve("CVE-NOT_FOUND", Transactional::None).await?;
let not_found = system
.get_vulnerability("CVE-NOT_FOUND", Transactional::None)
.await?;

assert!(not_found.is_none());

Expand Down Expand Up @@ -136,7 +138,9 @@ mod tests {
.ingest_vulnerability("CVE-8675309", Transactional::None)
.await?;

let cve = system.get_cve("CVE-8675309", Transactional::None).await?;
let cve = system
.get_vulnerability("CVE-8675309", Transactional::None)
.await?;

assert!(cve.is_some());

Expand Down
2 changes: 1 addition & 1 deletion importer/src/sbom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use sbom_walker::{
use std::process::ExitCode;
use std::time::SystemTime;
use time::{Date, Month, UtcOffset};
use trustify_graph::graph::Graph;
use trustify_common::config::Database;
use trustify_graph::graph::Graph;
use url::Url;
use walker_common::{fetcher::Fetcher, validate::ValidationOptions};

Expand Down
3 changes: 3 additions & 0 deletions ingestors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.114"
chrono = { version = "0.4.35", features = ["serde"] }
tokio = { version = "1.30.0", features = ["full"] }
thiserror = "1.0.58"
anyhow = "1.0.72"
log = "0.4.19"
env_logger = "0.10.0"
reqwest = "0.11"
ring = "0.17.8"
hex = "0.4.3"

[dev-dependencies]
test-log = { version = "0.2.15", features = ["env_logger", "trace"] }
Expand Down
3 changes: 1 addition & 2 deletions ingestors/src/cve/cve_record/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@

pub mod v5;
pub mod v5;
43 changes: 23 additions & 20 deletions ingestors/src/cve/cve_record/v5.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct CveRecord {
pub data_type: String,
pub data_version: String,
Expand All @@ -11,14 +11,23 @@ pub struct CveRecord {
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "state", rename_all="UPPERCASE")]
#[serde(tag = "state", rename_all = "UPPERCASE")]
pub enum CveMetadata {
Published(MetadataPublished),
Rejected(MetadataRejected),
}

impl CveMetadata {
pub fn cve_id(&self) -> &str {
match self {
CveMetadata::Published(inner) => &inner.cve_id,
CveMetadata::Rejected(inner) => &inner.cve_id,
}
}
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct MetadataPublished {
pub cve_id: String,
pub assigner_org_id: String,
Expand All @@ -31,7 +40,7 @@ pub struct MetadataPublished {
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct MetadataRejected {
pub cve_id: String,
pub assigner_org_id: String,
Expand All @@ -45,40 +54,40 @@ pub struct MetadataRejected {
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct Containers {
pub cna: CnaContainer,
pub adp: Option<AdpContainer>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct CnaContainer {
pub descriptions: Vec<Description>,
pub problem_types: Vec<ProblemType>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct AdpContainer {
pub descriptions: Vec<Description>,
}

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

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct ProblemType {
pub descriptions: Vec<ProblemTypeDescription>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="camelCase")]
#[serde(rename_all = "camelCase")]
pub struct ProblemTypeDescription {
pub lang: String,
pub description: String,
Expand All @@ -88,11 +97,11 @@ pub struct ProblemTypeDescription {

#[cfg(test)]
mod test {
use crate::cve::cve_record::v5::CveRecord;
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use test_log::test;
use crate::cve::cve_record::v5::CveRecord;

#[test(tokio::test)]
async fn serde() -> Result<(), anyhow::Error> {
Expand All @@ -103,16 +112,10 @@ mod test {

let cve_file = File::open(cve_json)?;

let cve: CveRecord = serde_json::from_reader(
cve_file
)?;
let cve: CveRecord = serde_json::from_reader(cve_file)?;

println!("{:#?}", cve);

Ok(())


}


}
}
112 changes: 112 additions & 0 deletions ingestors/src/cve/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::cve::cve_record::v5::CveRecord;
use crate::hashing::HashingRead;
use crate::Error;
use std::io::Read;
use trustify_graph::db::Transactional;
use trustify_graph::graph::Graph;

/// Loader capable of parsing a CVE Record JSON file
/// and manipulating the Graph to integrate it into
/// the knowledge base.
///
/// Should result in ensuring that a *vulnerability*
/// related to the CVE Record exists in the graph, _along with_
/// also ensuring that the CVE *advisory* ends up also
/// in the graph.
pub struct CveLoader<'g> {
graph: &'g Graph,
}

impl<'g> CveLoader<'g> {
pub fn new(graph: &'g Graph) -> Self {
Self { graph }
}

pub async fn load<L: Into<String>, R: Read>(
&self,
location: L,
record: R,
) -> Result<(), Error> {
let mut reader = HashingRead::new(record);
let cve: CveRecord = serde_json::from_reader(&mut reader)?;

self.graph
.ingest_vulnerability(cve.cve_metadata.cve_id(), Transactional::None)
.await?;

let hashes = reader.hashes();

let sha256 = hex::encode(hashes.sha256.as_ref());

self.graph
.ingest_advisory(
cve.cve_metadata.cve_id(),
location,
sha256,
Transactional::None,
)
.await?;

Ok(())
}
}

#[cfg(test)]
mod test {
use crate::cve::loader::CveLoader;
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use test_log::test;
use trustify_graph::db::Transactional;
use trustify_graph::graph::Graph;

#[test(tokio::test)]
async fn cve_loader() -> Result<(), anyhow::Error> {
let graph = Graph::for_test("ingestors_cve_loader").await?;

let pwd = PathBuf::from_str(env!("CARGO_MANIFEST_DIR"))?;
let test_data = pwd.join("../etc/test-data/mitre");

let cve_json = test_data.join("CVE-2024-28111.json");
let cve_file = File::open(cve_json)?;

let loaded_vulnerability = graph
.get_vulnerability("CVE-2024-28111", Transactional::None)
.await?;

assert!(loaded_vulnerability.is_none());

let loaded_advisory = graph
.get_advisory(
"CVE-2024-28111",
"CVE-2024-28111.json",
"06908108e8097f2a56e628e7814a7bd54a5fc95f645b7c9fab02c1eb8dd9cc0c",
)
.await?;

assert!(loaded_advisory.is_none());

let loader = CveLoader::new(&graph);

loader.load("CVE-2024-28111.json", cve_file).await?;

let loaded_vulnerability = graph
.get_vulnerability("CVE-2024-28111", Transactional::None)
.await?;

assert!(loaded_vulnerability.is_some());

let loaded_advisory = graph
.get_advisory(
"CVE-2024-28111",
"CVE-2024-28111.json",
"06908108e8097f2a56e628e7814a7bd54a5fc95f645b7c9fab02c1eb8dd9cc0c",
)
.await?;

assert!(loaded_advisory.is_some());

Ok(())
}
}
10 changes: 1 addition & 9 deletions ingestors/src/cve/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
use trustify_graph::graph::Graph;

pub mod cve_record;
pub mod loader;

pub struct CveIngestor<'g> {
graph: &'g Graph,

}

impl<'g> CveIngestor<'g> {

}
36 changes: 36 additions & 0 deletions ingestors/src/hashing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use ring::digest::Context;
use ring::digest::{Digest, SHA256};
use std::io::Read;

pub struct HashingRead<R: Read> {
inner: R,
sha256: Context,
}

#[derive(Debug)]
pub struct Hashes {
pub sha256: Digest,
}

impl<R: Read> HashingRead<R> {
pub fn new(inner: R) -> Self {
Self {
inner,
sha256: Context::new(&SHA256),
}
}

pub fn hashes(&self) -> Hashes {
Hashes {
sha256: self.sha256.clone().finish(),
}
}
}

impl<R: Read> Read for &mut HashingRead<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let len = self.inner.read(buf)?;
self.sha256.update(&buf[0..len]);
Ok(len)
}
}
22 changes: 21 additions & 1 deletion ingestors/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
pub mod cve;

pub mod hashing;

pub mod cve;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Json(serde_json::Error),
#[error(transparent)]
Graph(trustify_graph::graph::error::Error),
}

impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::Json(value)
}
}

impl From<trustify_graph::graph::error::Error> for Error {
fn from(value: trustify_graph::graph::error::Error) -> Self {
Self::Graph(value)
}
}
Loading

0 comments on commit e1cedcc

Please sign in to comment.