Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 74 additions & 6 deletions modules/fundamental/src/purl/model/details/purl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ use crate::{
vulnerability::model::VulnerabilityHead,
};
use sea_orm::{
ColumnTrait, ConnectionTrait, DbErr, EntityTrait, FromQueryResult, LoaderTrait, ModelTrait,
QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
SelectColumns,
ColumnTrait, Condition, ConnectionTrait, DbErr, EntityTrait, FromQueryResult, LoaderTrait,
ModelTrait, QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait,
Select, SelectColumns,
};
use sea_query::{
Alias, Asterisk, ColumnRef, Expr, Func, IntoIden, JoinType, SimpleExpr, UnionType,
};
use sea_query::{Asterisk, ColumnRef, Expr, Func, IntoIden, JoinType, SimpleExpr};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, hash_map::Entry};
use trustify_common::{
Expand All @@ -28,8 +30,8 @@ use trustify_common::{
use trustify_entity::{
advisory, advisory_vulnerability_score, base_purl, cpe, license, organization, product,
product_status, product_version, product_version_range, purl_status, qualified_purl, sbom,
sbom_license_expanded, sbom_node, sbom_node_purl_ref, sbom_package_license, status,
version_range, versioned_purl, vulnerability,
sbom_describing_cpe, sbom_license_expanded, sbom_node, sbom_node_purl_ref,
sbom_package_license, status, version_range, versioned_purl, vulnerability,
};
use trustify_module_ingestor::common::{Deprecation, DeprecationForExt};
use utoipa::ToSchema;
Expand Down Expand Up @@ -84,6 +86,66 @@ impl PurlDetails {
.ok_or(Error::Data("underlying package missing".to_string()))?
};

let sbom_ids_for_purl = sbom_node_purl_ref::Entity::find()
.select_only()
.column(sbom_node_purl_ref::Column::SbomId)
.filter(sbom_node_purl_ref::Column::QualifiedPurlId.eq(qualified_package.id))
.into_query();

let mut allowed_cpe_ids = sbom_describing_cpe::Entity::find()
.select_only()
.column(sbom_describing_cpe::Column::CpeId)
.filter(sbom_describing_cpe::Column::SbomId.in_subquery(sbom_ids_for_purl.clone()))
.into_query();

let c = Alias::new("c");
let sc = Alias::new("sc");
let sdc = Alias::new("sdc");
let generalized_cpe_ids = sea_query::Query::select()
.expr(Expr::col((c.clone(), cpe::Column::Id)))
.from_as(cpe::Entity, c.clone())
.join_as(
JoinType::InnerJoin,
cpe::Entity,
sc.clone(),
Condition::all()
.add(
Expr::col((c.clone(), cpe::Column::Vendor))
.equals((sc.clone(), cpe::Column::Vendor)),
)
.add(
Expr::col((c.clone(), cpe::Column::Product))
.equals((sc.clone(), cpe::Column::Product)),
)
.add(
Expr::col((c.clone(), cpe::Column::Version)).eq(SimpleExpr::FunctionCall(
Func::cust(Alias::new("split_part"))
.arg(Expr::col((sc.clone(), cpe::Column::Version)))
.arg(Expr::value("."))
.arg(Expr::value(1i32)),
)),
),
)
.join_as(
JoinType::InnerJoin,
sbom_describing_cpe::Entity,
sdc.clone(),
Expr::col((sdc.clone(), sbom_describing_cpe::Column::CpeId))
.equals((sc.clone(), cpe::Column::Id)),
)
.and_where(
Expr::col((sdc.clone(), sbom_describing_cpe::Column::SbomId))
.in_subquery(sbom_ids_for_purl.clone()),
)
.to_owned();
allowed_cpe_ids.union(UnionType::Distinct, generalized_cpe_ids);

let sbom_has_cpes = sea_query::Query::select()
.expr(Expr::value(1i32))
.from(sbom_describing_cpe::Entity)
.and_where(sbom_describing_cpe::Column::SbomId.in_subquery(sbom_ids_for_purl))
.to_owned();

let purl_statuses = purl_status::Entity::find()
.filter(purl_status::Column::BasePurlId.eq(package.id))
.left_join(version_range::Entity)
Expand All @@ -93,6 +155,12 @@ impl PurlDetails {
.arg(Expr::value(package_version.version.clone()))
.arg(Expr::col((version_range::Entity, Asterisk))),
))
.filter(
Condition::any()
.add(purl_status::Column::ContextCpeId.is_null())
.add(purl_status::Column::ContextCpeId.in_subquery(allowed_cpe_ids))
.add(Expr::exists(sbom_has_cpes).not()),
)
.distinct_on([ColumnRef::TableColumn(
purl_status::Entity.into_iden(),
purl_status::Column::Id.into_iden(),
Expand Down
11 changes: 4 additions & 7 deletions modules/fundamental/src/sbom/model/raw_sql.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// This constant is a SQL subquery that filters the context_cpe_id
/// based on the given sbom_id. It reads from the materialized
/// sbom_describing_cpe table instead of computing the join at query time.
/// The generalized CPE logic expands matches to include CPEs without edition
/// and with major-version-only matching.
/// The generalized CPE logic expands matches to include all CPEs sharing
/// the same vendor, product, and major version.
pub const CONTEXT_CPE_FILTER_SQL: &str = r#"
(
context_cpe_id IS NULL OR
Expand All @@ -16,8 +16,7 @@ pub const CONTEXT_CPE_FILTER_SQL: &str = r#"
generalized_cpes AS (
SELECT *
FROM cpe
WHERE (edition IS NULL OR edition = '*')
AND (vendor, product, version) IN (
WHERE (vendor, product, version) IN (
SELECT vendor, product, split_part(version, '.', 1)
FROM filtered_cpes
)
Expand Down Expand Up @@ -86,7 +85,6 @@ pub fn batch_severity_counts_sql() -> &'static str {
JOIN cpe c ON c.vendor = sc.vendor
AND c.product = sc.product
AND c.version = split_part(sc.version, '.', 1)
AND (c.edition IS NULL OR c.edition = '*')
),
sbom_allowed_cpes AS (
SELECT sbom_id, id AS cpe_id FROM sbom_cpes
Expand Down Expand Up @@ -218,8 +216,7 @@ pub fn product_advisory_info_sql() -> String {
generalized_cpes AS (
SELECT *
FROM cpe
WHERE (edition IS NULL OR edition = '*')
AND (vendor, product, version) IN (
WHERE (vendor, product, version) IN (
SELECT vendor, product, split_part(version, '.', 1)
FROM filtered_cpes
)
Expand Down
84 changes: 84 additions & 0 deletions modules/fundamental/src/vulnerability/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,38 @@ GROUP BY
AND base_purl.name = $2
AND base_purl.type = $3
AND version_matches($4, version_range.*) = TRUE
AND (
purl_status.context_cpe_id IS NULL
OR purl_status.context_cpe_id IN (
SELECT sdc.cpe_id
FROM sbom_describing_cpe sdc
JOIN sbom_node_purl_ref snpr ON snpr.sbom_id = sdc.sbom_id
JOIN qualified_purl qp ON snpr.qualified_purl_id = qp.id
JOIN versioned_purl vp ON qp.versioned_purl_id = vp.id
WHERE vp.base_purl_id = base_purl.id

UNION

SELECT c.id
FROM cpe c
JOIN cpe sc ON c.vendor = sc.vendor
AND c.product = sc.product
AND c.version = split_part(sc.version, '.', 1)
JOIN sbom_describing_cpe sdc ON sdc.cpe_id = sc.id
JOIN sbom_node_purl_ref snpr ON snpr.sbom_id = sdc.sbom_id
JOIN qualified_purl qp ON snpr.qualified_purl_id = qp.id
JOIN versioned_purl vp ON qp.versioned_purl_id = vp.id
WHERE vp.base_purl_id = base_purl.id
)
OR NOT EXISTS (
SELECT 1
FROM sbom_describing_cpe sdc
JOIN sbom_node_purl_ref snpr ON snpr.sbom_id = sdc.sbom_id
JOIN qualified_purl qp ON snpr.qualified_purl_id = qp.id
JOIN versioned_purl vp ON qp.versioned_purl_id = vp.id
WHERE vp.base_purl_id = base_purl.id
)
)
"#).as_str(),
"r.data",
);
Expand Down Expand Up @@ -612,6 +644,23 @@ GROUP BY
}
};

let product_bp_condition = match &purl.namespace {
Some(namespace) => {
Statement::from_sql_and_values(
connection.get_database_backend(),
"bp.name = $1 AND bp.type = $2 AND bp.namespace = $3",
[purl.name.clone().into(), purl.ty.clone().into(), namespace.into()],
).to_string()
}
None => {
Statement::from_sql_and_values(
connection.get_database_backend(),
"bp.name = $1 AND bp.type = $2 AND bp.namespace IS NULL",
[purl.name.clone().into(), purl.ty.clone().into()],
).to_string()
}
};

let product_status_sql = Self::build_vulnerabilities_query_string(
r#" 'advisory_id', product_status.advisory_id,
'context_cpe', cpe.id
Expand All @@ -629,6 +678,41 @@ GROUP BY
"#,
format!(r#" {package_condition}
AND product_status.package IS NOT NULL
AND (
product_status.context_cpe_id IS NULL
OR product_status.context_cpe_id IN (
SELECT sdc.cpe_id
FROM sbom_describing_cpe sdc
JOIN sbom_node_purl_ref snpr ON snpr.sbom_id = sdc.sbom_id
JOIN qualified_purl qp ON snpr.qualified_purl_id = qp.id
JOIN versioned_purl vp ON qp.versioned_purl_id = vp.id
JOIN base_purl bp ON vp.base_purl_id = bp.id
WHERE {product_bp_condition}

UNION

SELECT c.id
FROM cpe c
JOIN cpe sc ON c.vendor = sc.vendor
AND c.product = sc.product
AND c.version = split_part(sc.version, '.', 1)
JOIN sbom_describing_cpe sdc ON sdc.cpe_id = sc.id
JOIN sbom_node_purl_ref snpr ON snpr.sbom_id = sdc.sbom_id
JOIN qualified_purl qp ON snpr.qualified_purl_id = qp.id
JOIN versioned_purl vp ON qp.versioned_purl_id = vp.id
JOIN base_purl bp ON vp.base_purl_id = bp.id
WHERE {product_bp_condition}
)
OR NOT EXISTS (
SELECT 1
FROM sbom_describing_cpe sdc
JOIN sbom_node_purl_ref snpr ON snpr.sbom_id = sdc.sbom_id
JOIN qualified_purl qp ON snpr.qualified_purl_id = qp.id
JOIN versioned_purl vp ON qp.versioned_purl_id = vp.id
JOIN base_purl bp ON vp.base_purl_id = bp.id
WHERE {product_bp_condition}
)
)
"#).as_str(),
r#" jsonb_set(r.data, '{product_ids}',
COALESCE(
Expand Down
Loading