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 99f3d67 commit e79b51c
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 4 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
90 changes: 86 additions & 4 deletions modules/fundamental/src/sbom/model/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ 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::{
cpe::CpeCompare,
db::{multi_model::SelectIntoMultiModel, ConnectionOrTransaction, VersionMatches},
db::{
multi_model::{FromQueryResultMultiModel, SelectIntoMultiModel},
ConnectionOrTransaction, VersionMatches,
},
memo::Memo,
};
use trustify_entity::{
base_purl, purl_status,
advisory, base_purl, product, product_status, product_version, purl_status,
qualified_purl::{self},
sbom::{self},
sbom_node, sbom_package, sbom_package_purl_ref, version_range, versioned_purl,
sbom_node, sbom_package, sbom_package_purl_ref, status, version_range, versioned_purl,
};
use utoipa::ToSchema;

Expand Down Expand Up @@ -74,6 +80,22 @@ impl SbomDetails {
.all(tx)
.await?;

let product_advisory_info = sbom
.find_related(product_version::Entity)
.join(JoinType::LeftJoin, product_version::Relation::Product.def())
.join(JoinType::LeftJoin, product::Relation::Cpe.def())
.join(
JoinType::Join,
trustify_entity::cpe::Relation::ProductStatus.def(),
)
.join(JoinType::Join, product_status::Relation::Status.def())
.join(JoinType::Join, 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 @@ -82,6 +104,7 @@ impl SbomDetails {
advisories: SbomAdvisory::from_models(
&summary.clone().described_by,
&relevant_advisory_info,
&product_advisory_statuses,
tx,
)
.await?,
Expand All @@ -102,6 +125,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 @@ -206,6 +230,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 @@ -221,3 +269,37 @@ 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: advisory::Model,
product_status: product_status::Model,
cpe: trustify_entity::cpe::Model,
status: status::Model,
sbom: Option<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, "", advisory::Entity)?,
product_status: Self::from_query_result_multi_model(res, "", product_status::Entity)?,
cpe: Self::from_query_result_multi_model(res, "", trustify_entity::cpe::Entity)?,
status: Self::from_query_result_multi_model(res, "", status::Entity)?,
sbom: Self::from_query_result_multi_model_optional(res, "", sbom::Entity)?,
})
}
}

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

0 comments on commit e79b51c

Please sign in to comment.