Skip to content

Commit

Permalink
Thicken up the Vulnerability DTOs.
Browse files Browse the repository at this point in the history
Hydrate details into a Vulnerability even if ingested after first light reference.
Add some error-reporting if format auto-detection doesn't.
Ensure `vulnerabilities` field is included, even if an empty array.
  • Loading branch information
bobmcwhirter committed May 9, 2024
1 parent baa9e30 commit a8fb6b9
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 50 deletions.
4 changes: 4 additions & 0 deletions entity/src/vulnerability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
not_affected_package_version, vulnerability_description,
};
use sea_orm::entity::prelude::*;
use time::OffsetDateTime;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "vulnerability")]
Expand All @@ -11,6 +12,9 @@ pub struct Model {
pub id: i32,
pub identifier: String,
pub title: Option<String>,
pub published: Option<OffsetDateTime>,
pub modified: Option<OffsetDateTime>,
pub withdrawn: Option<OffsetDateTime>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand Down
10 changes: 10 additions & 0 deletions migration/src/m0000040_create_vulnerability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ impl MigrationTrait for Migration {
.not_null(),
)
.col(ColumnDef::new(Vulnerability::Title).string())
.col(ColumnDef::new(Vulnerability::Published).timestamp_with_time_zone())
.col(ColumnDef::new(Vulnerability::Modified).timestamp_with_time_zone())
.col(
ColumnDef::new(Vulnerability::Withdrawn)
.timestamp_with_time_zone()
.default(Func::cust(Now)),
)
.to_owned(),
)
.await
Expand All @@ -52,4 +59,7 @@ pub enum Vulnerability {
// --
Identifier,
Title,
Published,
Modified,
Withdrawn,
}
4 changes: 4 additions & 0 deletions modules/fetch/src/endpoints/advisory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ mod test {
title: Some("RHSA-1".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -152,6 +153,7 @@ mod test {
title: Some("RHSA-2".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -203,6 +205,7 @@ mod test {
title: Some("RHSA-1".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand All @@ -217,6 +220,7 @@ mod test {
title: Some("RHSA-2".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down
4 changes: 4 additions & 0 deletions modules/fetch/src/endpoints/vulnerability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ mod test {
title: Some("RHSA-1".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -270,6 +271,7 @@ mod test {
title: Some("RHSA-2".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -309,6 +311,7 @@ mod test {
title: Some("RHSA-1".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -341,6 +344,7 @@ mod test {
title: Some("RHSA-2".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down
5 changes: 4 additions & 1 deletion modules/fetch/src/model/vulnerability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ use utoipa::ToSchema;
pub struct VulnerabilityHead {
pub identifier: String,
pub title: Option<String>,
pub published: Option<OffsetDateTime>,
pub modified: Option<OffsetDateTime>,
pub withdrawn: Option<OffsetDateTime>,
}

#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
pub struct VulnerabilitySummary {
#[serde(flatten)]
pub head: VulnerabilityHead,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub advisories: Vec<VulnerabilityAdvisoryHead>,
}

Expand Down
4 changes: 4 additions & 0 deletions modules/fetch/src/service/advisory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ mod test {
title: Some("RHSA-1".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -452,6 +453,7 @@ mod test {
title: Some("RHSA-2".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -480,6 +482,7 @@ mod test {
title: Some("RHSA-1".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down Expand Up @@ -519,6 +522,7 @@ mod test {
title: Some("RHSA-2".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
withdrawn: None,
},
(),
)
Expand Down
12 changes: 12 additions & 0 deletions modules/fetch/src/service/vulnerability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ impl super::FetchService {
head: VulnerabilityHead {
identifier: vuln.identifier.clone(),
title: vuln.title.clone(),
published: vuln.published,
modified: vuln.modified,
withdrawn: vuln.withdrawn,
},
advisories: self
.vulnerability_advisory_heads(vuln.id, advisories, &tx)
Expand All @@ -269,6 +272,9 @@ impl super::FetchService {
.map(|vuln| VulnerabilityHead {
identifier: vuln.identifier.clone(),
title: vuln.title.clone(),
published: vuln.published,
modified: vuln.modified,
withdrawn: vuln.withdrawn,
})
.collect())
}
Expand Down Expand Up @@ -509,6 +515,9 @@ impl super::FetchService {
head: VulnerabilityHead {
identifier: vuln.identifier,
title: vuln.title,
published: vuln.published,
modified: vuln.modified,
withdrawn: vuln.withdrawn,
},
advisories: self
.vulnerability_advisory_heads(vuln.id, &advisories, &tx)
Expand Down Expand Up @@ -544,6 +553,9 @@ impl super::FetchService {
head: VulnerabilityHead {
identifier: vuln.identifier.clone(),
title: vuln.title.clone(),
published: vuln.published,
modified: vuln.modified,
withdrawn: vuln.withdrawn,
},
advisories,
}))
Expand Down
4 changes: 3 additions & 1 deletion modules/ingestor/src/graph/advisory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct AdvisoryInformation {
pub title: Option<String>,
pub published: Option<OffsetDateTime>,
pub modified: Option<OffsetDateTime>,
pub withdrawn: Option<OffsetDateTime>,
}

impl From<()> for AdvisoryInformation {
Expand Down Expand Up @@ -76,6 +77,7 @@ impl Graph {
title,
published,
modified,
..
} = information.into();

let model = entity::advisory::ActiveModel {
Expand Down Expand Up @@ -186,7 +188,7 @@ impl<'g> AdvisoryContext<'g> {
return Ok(found);
}

let vulnerability = self.graph.ingest_vulnerability(identifier, &tx).await?;
let vulnerability = self.graph.ingest_vulnerability(identifier, (), &tx).await?;

let entity = entity::advisory_vulnerability::ActiveModel {
advisory_id: Set(self.advisory.id),
Expand Down
70 changes: 51 additions & 19 deletions modules/ingestor/src/graph/vulnerability/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,71 @@ use crate::graph::error::Error;
use crate::graph::Graph;
use sea_orm::ActiveValue::Set;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, NotSet, QueryFilter, QuerySelect,
RelationTrait,
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QuerySelect, RelationTrait,
};
use sea_query::JoinType;
use std::fmt::{Debug, Formatter};
use time::OffsetDateTime;
use trustify_common::db::Transactional;
use trustify_entity as entity;
use trustify_entity::{advisory, advisory_vulnerability, vulnerability, vulnerability_description};

#[derive(Clone, Default)]
pub struct VulnerabilityInformation {
pub title: Option<String>,
pub published: Option<OffsetDateTime>,
pub modified: Option<OffsetDateTime>,
pub withdrawn: Option<OffsetDateTime>,
}

impl VulnerabilityInformation {
pub fn has_data(&self) -> bool {
self.title.is_some()
|| self.published.is_some()
|| self.modified.is_some()
|| self.withdrawn.is_some()
}
}

impl From<()> for VulnerabilityInformation {
fn from(_: ()) -> Self {
Self {
title: None,
published: None,
modified: None,
withdrawn: None,
}
}
}

impl Graph {
pub async fn ingest_vulnerability<TX: AsRef<Transactional>>(
pub async fn ingest_vulnerability<
I: Into<VulnerabilityInformation>,
TX: AsRef<Transactional>,
>(
&self,
identifier: &str,
information: I,
tx: TX,
) -> Result<VulnerabilityContext, Error> {
let information = information.into();
if let Some(found) = self.get_vulnerability(identifier, &tx).await? {
Ok(found)
if information.has_data() {
// we should update with the details.
let entity = vulnerability::ActiveModel::from(found.vulnerability);
let model = entity.update(&self.connection(&tx)).await?;
Ok(VulnerabilityContext::new(&found.graph, model))
} else {
Ok(found)
}
} else {
let entity = vulnerability::ActiveModel {
id: Default::default(),
identifier: Set(identifier.to_string()),
title: NotSet,
title: Set(information.title),
published: Set(information.published),
modified: Set(information.modified),
withdrawn: Set(information.withdrawn),
};

Ok(VulnerabilityContext::new(
Expand Down Expand Up @@ -86,17 +129,6 @@ impl VulnerabilityContext {
.collect())
}

pub async fn set_title<TX: AsRef<Transactional>>(
&self,
title: Option<String>,
tx: TX,
) -> Result<(), Error> {
let mut entity: vulnerability::ActiveModel = self.vulnerability.clone().into();
entity.title = Set(title);
entity.save(&self.graph.connection(&tx)).await?;
Ok(())
}

pub async fn add_description<TX: AsRef<Transactional>>(
&self,
lang: &str,
Expand Down Expand Up @@ -147,13 +179,13 @@ mod tests {
let system = Graph::new(db);

let cve1 = system
.ingest_vulnerability("CVE-123", Transactional::None)
.ingest_vulnerability("CVE-123", (), Transactional::None)
.await?;
let cve2 = system
.ingest_vulnerability("CVE-123", Transactional::None)
.ingest_vulnerability("CVE-123", (), Transactional::None)
.await?;
let cve3 = system
.ingest_vulnerability("CVE-456", Transactional::None)
.ingest_vulnerability("CVE-456", (), Transactional::None)
.await?;

assert_eq!(cve1.vulnerability.id, cve2.vulnerability.id);
Expand Down
1 change: 1 addition & 0 deletions modules/ingestor/src/service/advisory/csaf/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ impl<'a> From<Information<'a>> for AdvisoryInformation {
value.document.tracking.current_release_date.timestamp(),
)
.ok(),
withdrawn: None,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions modules/ingestor/src/service/advisory/osv/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl<'g> OsvLoader<'g> {
title: osv.summary.clone(),
published: Some(osv.published),
modified: Some(osv.modified),
withdrawn: osv.withdrawn,
};
let advisory = self
.graph
Expand Down
Loading

0 comments on commit a8fb6b9

Please sign in to comment.