From d447438c89b0259213f3c601d665d301cfa1b1a2 Mon Sep 17 00:00:00 2001 From: tdruez Date: Mon, 3 Jun 2024 17:07:33 +0400 Subject: [PATCH] Add full dependencies tree in CDX output #1066 Signed-off-by: tdruez --- scanpipe/pipes/output.py | 16 ++++++++++++++++ scanpipe/tests/pipes/test_output.py | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/scanpipe/pipes/output.py b/scanpipe/pipes/output.py index 0d0d6b81d..e02cc7427 100644 --- a/scanpipe/pipes/output.py +++ b/scanpipe/pipes/output.py @@ -676,6 +676,7 @@ def get_cyclonedx_bom(project): ) vulnerabilities = [] + dependencies = {} package_qs = get_queryset(project, "discoveredpackage") package_qs = package_qs.prefetch_related("children_packages") @@ -685,11 +686,26 @@ def get_cyclonedx_bom(project): bom.components.add(component) bom.register_dependency(project_as_root_component, [component]) + # Store the component dependencies to be added later since all components need + # to be added on the BOM first. + dependencies[component] = [ + package.cyclonedx_bom_ref for package in package.children_packages.all() + ] + for vulnerability_data in package.affected_by_vulnerabilities: vulnerabilities.append( vulnerability_as_cyclonedx(vulnerability_data, component.bom_ref) ) + for component, depends_on_bom_refs in dependencies.items(): + if not depends_on_bom_refs: + continue + # Craft disposable Component instances for registering dependencies + dependencies = [ + cdx_component.Component(name="", bom_ref=ref) for ref in depends_on_bom_refs + ] + bom.register_dependency(component, dependencies) + bom.vulnerabilities = vulnerabilities return bom diff --git a/scanpipe/tests/pipes/test_output.py b/scanpipe/tests/pipes/test_output.py index f634a8e9a..664c82a8c 100644 --- a/scanpipe/tests/pipes/test_output.py +++ b/scanpipe/tests/pipes/test_output.py @@ -44,6 +44,8 @@ from scanpipe.models import ProjectMessage from scanpipe.pipes import output from scanpipe.tests import FIXTURES_REGEN +from scanpipe.tests import make_dependency +from scanpipe.tests import make_package from scanpipe.tests import mocked_now from scanpipe.tests import package_data1 @@ -281,6 +283,31 @@ def test_scanpipe_pipes_outputs_to_cyclonedx(self, regen=FIXTURES_REGEN): ) self.assertEqual("1.5", results_json["specVersion"]) + def test_scanpipe_pipes_outputs_get_cyclonedx_bom_dependency_tree(self): + project = Project.objects.create(name="project") + + a = make_package(project, "pkg:type/a") + b = make_package(project, "pkg:type/b") + c = make_package(project, "pkg:type/c") + + # A -> B -> C + make_dependency(project, for_package=a, resolved_to_package=b) + make_dependency(project, for_package=b, resolved_to_package=c) + + output_file = output.to_cyclonedx(project=project) + results_json = json.loads(output_file.read_text()) + + expected = [ + { + "dependsOn": ["pkg:type/a", "pkg:type/b", "pkg:type/c"], + "ref": str(project.uuid), + }, + {"dependsOn": ["pkg:type/b"], "ref": "pkg:type/a"}, + {"dependsOn": ["pkg:type/c"], "ref": "pkg:type/b"}, + {"ref": "pkg:type/c"}, + ] + self.assertEqual(expected, results_json["dependencies"]) + def test_scanpipe_pipes_outputs_to_spdx(self): fixtures = self.data_path / "asgiref-3.3.0_fixtures.json" call_command("loaddata", fixtures, **{"verbosity": 0})