Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit eb66c3d

Browse files
committedFeb 3, 2025··
fix: insert affected status for versions up to the fixed ones
1 parent c10d89a commit eb66c3d

File tree

3 files changed

+2670
-34
lines changed

3 files changed

+2670
-34
lines changed
 

‎etc/datasets/ds3/csaf/2024/cve-2024-50602.json

+2,567
Large diffs are not rendered by default.

‎modules/fundamental/tests/dataset.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async fn ingest(ctx: TrustifyContext) -> anyhow::Result<()> {
5656
log::info!("ingest: {}", humantime::Duration::from(ingest_time));
5757

5858
assert!(result.warnings.is_empty());
59-
assert_eq!(result.files.len(), 66);
59+
assert_eq!(result.files.len(), 67);
6060

6161
// get a document
6262

@@ -109,7 +109,24 @@ async fn ingest(ctx: TrustifyContext) -> anyhow::Result<()> {
109109
.any(|sbom_status| sbom_status.status == "affected")
110110
})
111111
.collect::<Vec<_>>();
112-
assert_eq!(advisories_affected.len(), 11);
112+
assert_eq!(advisories_affected.len(), 13);
113+
114+
//TODO convert this test in e2e test for ds3
115+
// ubi
116+
let ubi = &result.files["spdx/ubi8-8.8-1067.json.bz2"];
117+
118+
let ubi_details = service
119+
.fetch_sbom_details(ubi.id.clone(), vec![], &ctx.db)
120+
.await?;
121+
assert!(ubi_details.is_some());
122+
let ubi_details = ubi_details.unwrap();
123+
let ubi_advisories = ubi_details.advisories;
124+
assert_eq!(ubi_advisories.len(), 2);
125+
assert!(ubi_advisories
126+
.iter()
127+
.map(|adv| adv.head.document_id.clone())
128+
.collect::<Vec<_>>()
129+
.contains(&"CVE-2024-50602".to_string()));
113130

114131
// done
115132

‎modules/ingestor/src/service/advisory/csaf/creator.rs

+84-32
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use uuid::Uuid;
2727
struct PurlStatus {
2828
cpe: Option<Cpe>,
2929
purl: Purl,
30-
status: &'static str,
30+
status: String,
3131
info: VersionInfo,
3232
}
3333

@@ -105,16 +105,18 @@ impl<'a> StatusCreator<'a> {
105105
connection: &C,
106106
) -> Result<(), Error> {
107107
let mut checked = HashMap::new();
108-
let mut product_statuses = Vec::new();
108+
let mut product_status_models = Vec::new();
109109
let mut purls = PurlCreator::new();
110110
let mut cpes = CpeCreator::new();
111111

112-
let mut org_cache: HashMap<&String, organization::Model> = HashMap::new();
113-
let mut products = Vec::new();
112+
let mut org_cache: HashMap<String, organization::Model> = HashMap::new();
113+
let mut product_models = Vec::new();
114114
let mut version_ranges = Vec::new();
115115
let mut product_version_ranges = Vec::new();
116116

117-
for product in &self.products {
117+
let product_statuses = self.products.clone();
118+
119+
for product in product_statuses {
118120
// ensure a correct status, and get id
119121
if let Entry::Vacant(entry) = checked.entry(product.status) {
120122
entry.insert(Self::check_status(product.status, connection).await?);
@@ -130,21 +132,26 @@ impl<'a> StatusCreator<'a> {
130132
// so simple caching should work here.
131133
// If we find examples where this is not a case, we can switch to
132134
// batch ingesting of organizations as well.
133-
let org_id = match &product.vendor {
134-
Some(vendor) => match org_cache.get(vendor) {
135+
let org_id = match product.vendor.clone() {
136+
Some(vendor) => match org_cache.get(&vendor) {
135137
Some(entry) => Some(entry.id),
136138
None => {
137139
let organization_cpe_key = product
138140
.cpe
139141
.clone()
140142
.map(|cpe| cpe.vendor().as_ref().to_string());
143+
141144
let org = OrganizationInformation {
142145
cpe_key: organization_cpe_key,
143146
website: None,
144147
};
148+
145149
let org: OrganizationContext<'_> =
146-
graph.ingest_organization(vendor, org, connection).await?;
147-
org_cache.entry(vendor).or_insert(org.organization.clone());
150+
graph.ingest_organization(&vendor, org, connection).await?;
151+
org_cache
152+
.entry(vendor.clone())
153+
.or_insert(org.organization.clone());
154+
148155
Some(org.organization.id)
149156
}
150157
},
@@ -165,7 +172,7 @@ impl<'a> StatusCreator<'a> {
165172
vendor_id: Set(org_id),
166173
cpe_key: Set(product_cpe_key),
167174
};
168-
products.push(product_entity.clone());
175+
product_models.push(product_entity.clone());
169176

170177
// Create all product ranges for batch ingesting
171178
let product_version_range = match product.version {
@@ -220,32 +227,49 @@ impl<'a> StatusCreator<'a> {
220227
cpes.add(cpe.clone());
221228
}
222229

223-
product_statuses.push(base_product);
230+
product_status_models.push(base_product);
224231
}
225232
}
226233

227234
for purl in &product.purls {
228-
let purl = purl.clone();
229-
// Ingest purl status
230-
let info = match purl.version.clone() {
231-
Some(version) => VersionInfo {
232-
scheme: VersionScheme::Generic,
233-
spec: VersionSpec::Exact(version),
234-
},
235-
None => VersionInfo {
236-
spec: VersionSpec::Range(Version::Unbounded, Version::Unbounded),
237-
scheme: VersionScheme::Semver,
238-
},
239-
};
235+
let scheme = Self::from_purl_version_scheme(purl);
240236

241-
let purl_status = PurlStatus {
242-
cpe: product.cpe.clone(),
243-
purl: purl.clone(),
244-
status: product.status,
245-
info,
237+
// Default case: Exact version or unbounded range
238+
let spec = match &purl.version {
239+
Some(version) => VersionSpec::Exact(version.clone()),
240+
None => VersionSpec::Range(Version::Unbounded, Version::Unbounded),
246241
};
247242

248-
self.entries.insert(purl_status);
243+
self.create_purl_status(&product, purl, scheme, spec, product.status.to_string());
244+
245+
// Special case for "fixed" status and RedHat CPE
246+
if product.status == "fixed" {
247+
if let Some(cpe_vendor) = product
248+
.cpe
249+
.as_ref()
250+
.map(|cpe| cpe.vendor().as_ref().to_string())
251+
{
252+
if cpe_vendor == "redhat" {
253+
if let Some(version) = &purl.version {
254+
//TODO improve status checking as we call db way too often for this simple task
255+
if let Entry::Vacant(entry) = checked.entry("affected") {
256+
entry.insert(Self::check_status("affected", connection).await?);
257+
};
258+
let spec = VersionSpec::Range(
259+
Version::Unbounded,
260+
Version::Exclusive(version.clone()),
261+
);
262+
self.create_purl_status(
263+
&product,
264+
purl,
265+
scheme,
266+
spec,
267+
"affected".to_string(),
268+
);
269+
}
270+
}
271+
}
272+
}
249273
}
250274
}
251275

@@ -265,7 +289,7 @@ impl<'a> StatusCreator<'a> {
265289

266290
self.create_status(connection, checked).await?;
267291

268-
for batch in &products.chunked() {
292+
for batch in &product_models.chunked() {
269293
product::Entity::insert_many(batch)
270294
.on_conflict_do_nothing()
271295
.exec(connection)
@@ -286,7 +310,7 @@ impl<'a> StatusCreator<'a> {
286310
.await?;
287311
}
288312

289-
for batch in &product_statuses.chunked() {
313+
for batch in &product_status_models.chunked() {
290314
product_status::Entity::insert_many(batch)
291315
.exec(connection)
292316
.await?;
@@ -297,6 +321,34 @@ impl<'a> StatusCreator<'a> {
297321
Ok(())
298322
}
299323

324+
fn from_purl_version_scheme(purl: &Purl) -> VersionScheme {
325+
match purl.ty.as_str() {
326+
"maven" => VersionScheme::Maven,
327+
"npm" => VersionScheme::Semver,
328+
"python" => VersionScheme::Python,
329+
"rpm" => VersionScheme::Rpm,
330+
"semver" => VersionScheme::Semver,
331+
_ => VersionScheme::Generic,
332+
}
333+
}
334+
335+
fn create_purl_status(
336+
&mut self,
337+
product: &ProductStatus,
338+
purl: &Purl,
339+
scheme: VersionScheme,
340+
spec: VersionSpec,
341+
status: String,
342+
) {
343+
let purl_status = PurlStatus {
344+
cpe: product.cpe.clone(),
345+
purl: purl.clone(),
346+
status,
347+
info: VersionInfo { scheme, spec },
348+
};
349+
self.entries.insert(purl_status);
350+
}
351+
300352
#[instrument(skip(self, connection), ret)]
301353
async fn create_status(
302354
&self,
@@ -307,7 +359,7 @@ impl<'a> StatusCreator<'a> {
307359
let mut package_statuses = Vec::new();
308360

309361
for ps in &self.entries {
310-
let status = checked.get(&ps.status).ok_or_else(|| {
362+
let status = checked.get(&ps.status.as_str()).ok_or_else(|| {
311363
Error::Graph(crate::graph::error::Error::InvalidStatus(
312364
ps.status.to_string(),
313365
))

0 commit comments

Comments
 (0)
Please sign in to comment.