From 63f9ada4e39fc8b292d52e12e5011232c1e0838f Mon Sep 17 00:00:00 2001 From: Jim Crossley Date: Mon, 20 Jan 2025 16:51:42 -0500 Subject: [PATCH] feat: create VariantOf relationship from cdx pedigree/variants Fixes #1147 Only analysis endpoints affected, no change to /api/v2/purl endpoint. Signed-off-by: Jim Crossley --- etc/test-data/cyclonedx/66FF73123BB3489.json | 77 +++++++++++++++++++ modules/analysis/src/endpoints.rs | 36 +++++++++ .../src/graph/purl/package_version.rs | 17 ---- modules/ingestor/src/graph/sbom/cyclonedx.rs | 26 +++++++ 4 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 etc/test-data/cyclonedx/66FF73123BB3489.json diff --git a/etc/test-data/cyclonedx/66FF73123BB3489.json b/etc/test-data/cyclonedx/66FF73123BB3489.json new file mode 100644 index 000000000..882845296 --- /dev/null +++ b/etc/test-data/cyclonedx/66FF73123BB3489.json @@ -0,0 +1,77 @@ +{ + "version": 1, + "metadata": { + "tools": { + "components": [ + { + "name": "SBOMer", + "type": "application", + "author": "Red Hat", + "version": "dce92b5d" + } + ] + }, + "component": { + "name": "openshift/ose-console", + "purl": "pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "type": "container", + "description": "Image index manifest of pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679" + }, + "timestamp": "2024-12-19T18:04:12Z" + }, + "bomFormat": "CycloneDX", + "components": [ + { + "name": "openshift/ose-console", + "purl": "pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "type": "container", + "bom-ref": "pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "version": "sha256:94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "pedigree": { + "variants": [ + { + "name": "openshift/ose-console", + "purl": "pkg:oci/ose-console@sha256%3Ac2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e?arch=s390x&os=linux&tag=v4.14.0-202412110104.p0.g350e1ea.assembly.stream.el8", + "type": "container", + "bom-ref": "pkg:oci/ose-console@sha256%3Ac2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e?arch=s390x&os=linux&tag=v4.14.0-202412110104.p0.g350e1ea.assembly.stream.el8", + "version": "sha256:c2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e", + "supplier": { + "url": [ + "https://www.redhat.com" + ], + "name": "Red Hat" + }, + "publisher": "Red Hat" + } + ] + }, + "supplier": { + "url": [ + "https://www.redhat.com" + ], + "name": "Red Hat" + }, + "publisher": "Red Hat", + "properties": [ + { + "name": "sbomer:image:labels:com.redhat.component", + "value": "openshift-enterprise-console-container" + }, + { + "name": "sbomer:image:labels:name", + "value": "openshift/ose-console" + }, + { + "name": "sbomer:image:labels:release", + "value": "202412110104.p0.g350e1ea.assembly.stream.el8" + }, + { + "name": "sbomer:image:labels:version", + "value": "v4.14.0" + } + ] + } + ], + "specVersion": "1.6", + "serialNumber": "urn:uuid:537c8dc3-6f66-3cac-b504-cc5fb0a09ece" +} diff --git a/modules/analysis/src/endpoints.rs b/modules/analysis/src/endpoints.rs index 69363199a..ffefa0df2 100644 --- a/modules/analysis/src/endpoints.rs +++ b/modules/analysis/src/endpoints.rs @@ -575,4 +575,40 @@ mod test { Ok(()) } + + #[test_context(TrustifyContext)] + #[test(actix_web::test)] + async fn issue_tc_2052(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["cyclonedx/66FF73123BB3489.json"]) + .await?; + + // Find all deps of parent + let parent = "pkg:oci/openshift-ose-console@sha256:94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679"; + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(parent)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!(1, response["items"][0]["deps"].as_array().unwrap().len()); + + // Ensure child is variant of src + let child = "pkg:oci/ose-console@sha256:c2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e?arch=s390x&os=linux&tag=v4.14.0-202412110104.p0.g350e1ea.assembly.stream.el8"; + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(child) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + "VariantOf", + response["items"][0]["ancestors"][0]["relationship"] + ); + assert_eq!( + Value::from(vec![Value::from(parent)]), + response["items"][0]["ancestors"][0]["purl"] + ); + + Ok(()) + } } diff --git a/modules/ingestor/src/graph/purl/package_version.rs b/modules/ingestor/src/graph/purl/package_version.rs index b7035c100..e761e3356 100644 --- a/modules/ingestor/src/graph/purl/package_version.rs +++ b/modules/ingestor/src/graph/purl/package_version.rs @@ -67,21 +67,4 @@ impl<'g> PackageVersionContext<'g> { Ok(found.map(|model| QualifiedPackageContext::new(self, model))) } - - /// Retrieve known variants of this package version. - /// - /// Non-mutating to the fetch. - pub async fn get_variants( - &self, - _pkg: Purl, - connection: &C, - ) -> Result, Error> { - Ok(entity::qualified_purl::Entity::find() - .filter(entity::qualified_purl::Column::VersionedPurlId.eq(self.package_version.id)) - .all(connection) - .await? - .into_iter() - .map(|base| QualifiedPackageContext::new(self, base)) - .collect()) - } } diff --git a/modules/ingestor/src/graph/sbom/cyclonedx.rs b/modules/ingestor/src/graph/sbom/cyclonedx.rs index 3e1b8d772..6e5000db5 100644 --- a/modules/ingestor/src/graph/sbom/cyclonedx.rs +++ b/modules/ingestor/src/graph/sbom/cyclonedx.rs @@ -365,6 +365,32 @@ impl<'a> ComponentCreator<'a> { self.relationships .relate(node_id.clone(), Relationship::AncestorOf, target); } + + for variant in comp + .pedigree + .iter() + .flat_map(|pedigree| pedigree.variants.iter().flatten()) + { + let target = variant + .bom_ref + .clone() + .unwrap_or_else(|| Uuid::new_v4().to_string()); + + // create the component + + let creator = ComponentCreator::new( + self.cpes, + self.purls, + self.licenses, + self.packages, + self.relationships, + ); + + creator.create(variant); + + self.relationships + .relate(target, Relationship::VariantOf, node_id.clone()); + } } pub fn add_cpe(&mut self, cpe: Cpe) {