Skip to content

Commit

Permalink
feat: csaf correlation - correlate sbom to vulnerabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
dejanb committed Nov 4, 2024
1 parent ace3750 commit e3a636c
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 2 deletions.
12 changes: 12 additions & 0 deletions entity/src/cpe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ pub enum Relation {
to = "super::product::Column::CpeKey"
)]
Product,
#[sea_orm(
belongs_to = "super::product_status::Entity",
from = "Column::Id",
to = "super::product_status::Column::ContextCpeId"
)]
ProductStatus,
}

impl Related<super::sbom_package_cpe_ref::Entity> for Entity {
Expand All @@ -48,6 +54,12 @@ impl Related<super::product::Entity> for Entity {
}
}

impl Related<super::product_status::Entity> for Entity {
fn to() -> RelationDef {
Relation::ProductStatus.def()
}
}

impl ActiveModelBehavior for ActiveModel {}

impl Display for Model {
Expand Down
100 changes: 98 additions & 2 deletions modules/fundamental/src/sbom/model/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ use crate::{
};
use async_graphql::SimpleObject;
use cpe::uri::OwnedUri;
use sea_orm::{JoinType, ModelTrait, QueryFilter, QuerySelect, RelationTrait};
use sea_orm::{
DbErr, EntityTrait, FromQueryResult, JoinType, ModelTrait, QueryFilter, QueryResult,
QuerySelect, RelationTrait, Select,
};
use sea_query::{Asterisk, Expr, Func, SimpleExpr};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use trustify_common::db::multi_model::SelectIntoMultiModel;
use trustify_common::db::multi_model::{FromQueryResultMultiModel, SelectIntoMultiModel};
use trustify_common::db::VersionMatches;
use trustify_common::{cpe::CpeCompare, db::ConnectionOrTransaction, memo::Memo};
use trustify_entity::{self as entity};
use trustify_entity::{
base_purl, purl_status,
qualified_purl::{self},
Expand Down Expand Up @@ -72,6 +76,34 @@ impl SbomDetails {
.all(tx)
.await?;

let product_advisory_info = sbom
.find_related(trustify_entity::product_version::Entity)
.join(
JoinType::LeftJoin,
trustify_entity::product_version::Relation::Product.def(),
)
.join(
JoinType::LeftJoin,
trustify_entity::product::Relation::Cpe.def(),
)
.join(
JoinType::Join,
trustify_entity::cpe::Relation::ProductStatus.def(),
)
.join(
JoinType::Join,
trustify_entity::product_status::Relation::Status.def(),
)
.join(
JoinType::Join,
trustify_entity::product_status::Relation::Advisory.def(),
);

let product_advisory_statuses = product_advisory_info
.try_into_multi_model::<ProductStatusCatcher>()?
.all(tx)
.await?;

let summary = SbomSummary::from_entity((sbom, node), service, tx).await?;

Ok(match summary {
Expand All @@ -80,6 +112,7 @@ impl SbomDetails {
advisories: SbomAdvisory::from_models(
&summary.clone().described_by,
&relevant_advisory_info,
&product_advisory_statuses,
tx,
)
.await?,
Expand All @@ -100,6 +133,7 @@ impl SbomAdvisory {
pub async fn from_models(
described_by: &[SbomPackage],
statuses: &[QueryCatcher],
product_statuses: &[ProductStatusCatcher],
tx: &ConnectionOrTransaction<'_>,
) -> Result<Vec<Self>, Error> {
let mut advisories = HashMap::new();
Expand Down Expand Up @@ -204,6 +238,30 @@ impl SbomAdvisory {
});
}

for product in product_statuses {
let advisory_cpe: Option<OwnedUri> = (&product.cpe).try_into().ok();
let status = SbomStatus {
vulnerability_id: product.product_status.vulnerability_id.clone(),
status: product.status.slug.clone(),
context: advisory_cpe
.as_ref()
.map(|e| StatusContext::Cpe(e.to_string())),
packages: vec![], // TODO find packages based on component
};
let advisory = SbomAdvisory {
head: AdvisoryHead::from_advisory(&product.advisory, Memo::NotProvided, tx).await?,
status: vec![status.clone()],
};

advisories
.entry(product.advisory.id)
.and_modify(|value| {
//Should we add logic here to add only most relevant entry?
value.status.push(status.clone())
})
.or_insert(advisory.clone());
}

Ok(advisories.values().cloned().collect::<Vec<_>>())
}
}
Expand All @@ -219,3 +277,41 @@ pub struct SbomStatus {
}

impl SbomStatus {}

#[derive(Debug)]
#[allow(dead_code)] //TODO sbom field is not used at the moment, but we will probably need it for graph search
pub struct ProductStatusCatcher {
advisory: entity::advisory::Model,
product_status: entity::product_status::Model,
cpe: entity::cpe::Model,
status: entity::status::Model,
sbom: Option<entity::sbom::Model>,
}

impl FromQueryResult for ProductStatusCatcher {
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
Ok(Self {
advisory: Self::from_query_result_multi_model(res, "", entity::advisory::Entity)?,
product_status: Self::from_query_result_multi_model(
res,
"",
entity::product_status::Entity,
)?,
cpe: Self::from_query_result_multi_model(res, "", entity::cpe::Entity)?,
status: Self::from_query_result_multi_model(res, "", entity::status::Entity)?,
sbom: Self::from_query_result_multi_model_optional(res, "", entity::sbom::Entity)?,
})
}
}

impl FromQueryResultMultiModel for ProductStatusCatcher {
fn try_into_multi_model<E: EntityTrait>(select: Select<E>) -> Result<Select<E>, DbErr> {
select
.try_model_columns(entity::advisory::Entity)?
.try_model_columns(entity::product_status::Entity)?
.try_model_columns(entity::cpe::Entity)?
.try_model_columns(entity::status::Entity)?
.try_model_columns(entity::product_version::Entity)?
.try_model_columns(entity::sbom::Entity)
}
}

0 comments on commit e3a636c

Please sign in to comment.