From b421f0a9e4db589e065c3db078a6675fcf4cabf8 Mon Sep 17 00:00:00 2001 From: Jude Niroshan Date: Sat, 27 Apr 2024 21:19:47 +0200 Subject: [PATCH] fix: revive root component in generated sbom for python pip Signed-off-by: Jude Niroshan --- .../exhort/providers/PythonPipProvider.java | 22 ++------- .../expected_component_sbom.json | 46 +++++++++++++++++- .../expected_stack_sbom.json | 46 +++++++++++++++++- .../expected_component_sbom.json | 47 ++++++++++++++++++ .../expected_stack_sbom.json | 48 ++++++++++++++++++- 5 files changed, 188 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/redhat/exhort/providers/PythonPipProvider.java b/src/main/java/com/redhat/exhort/providers/PythonPipProvider.java index da3a83e7..e94125c6 100644 --- a/src/main/java/com/redhat/exhort/providers/PythonPipProvider.java +++ b/src/main/java/com/redhat/exhort/providers/PythonPipProvider.java @@ -42,6 +42,8 @@ public final class PythonPipProvider extends Provider { private Logger log = LoggersFactory.getLogger(this.getClass().getName()); + private static final String DEFAULT_PIP_ROOT_COMPONENT_NAME = "default-pip-root"; + private static final String DEFAULT_PIP_ROOT_COMPONENT_VERSION = "0.0.0"; public void setPythonController(PythonControllerBase pythonController) { this.pythonController = pythonController; @@ -76,11 +78,7 @@ public Content provideStack(Path manifestPath) throws IOException { pythonController.getDependencies(manifestPath.toString(), true); printDependenciesTree(dependencies); Sbom sbom = SbomFactory.newInstance(Sbom.BelongingCondition.PURL, "sensitive"); - try { - sbom.addRoot(new PackageURL(Ecosystem.Type.PYTHON.getType(), "root")); - } catch (MalformedPackageURLException e) { - throw new RuntimeException(e); - } + sbom.addRoot(toPurl(DEFAULT_PIP_ROOT_COMPONENT_NAME, DEFAULT_PIP_ROOT_COMPONENT_VERSION)); dependencies.stream() .forEach( (component) -> { @@ -88,10 +86,6 @@ public Content provideStack(Path manifestPath) throws IOException { }); byte[] requirementsFile = Files.readAllBytes(manifestPath); handleIgnoredDependencies(new String(requirementsFile), sbom); - // In python' pip requirements.txt, there is no real root element, then need to remove dummy - // root element that - // was created for creating the sbom. - sbom.removeRootComponent(); return new Content( sbom.getAsJsonString().getBytes(StandardCharsets.UTF_8), Api.CYCLONEDX_MEDIA_TYPE); } @@ -132,11 +126,7 @@ public Content provideComponent(byte[] manifestContent) throws IOException { pythonController.getDependencies(manifestPath.toString(), false); printDependenciesTree(dependencies); Sbom sbom = SbomFactory.newInstance(); - try { - sbom.addRoot(new PackageURL(Ecosystem.Type.PYTHON.getType(), "root")); - } catch (MalformedPackageURLException e) { - throw new RuntimeException(e); - } + sbom.addRoot(toPurl(DEFAULT_PIP_ROOT_COMPONENT_NAME, DEFAULT_PIP_ROOT_COMPONENT_VERSION)); dependencies.stream() .forEach( (component) -> { @@ -147,10 +137,6 @@ public Content provideComponent(byte[] manifestContent) throws IOException { Files.delete(manifestPath); Files.delete(tempRepository); handleIgnoredDependencies(new String(manifestContent), sbom); - // In python' pip requirements.txt, there is no real root element, then need to remove dummy - // root element that - // was created for creating the sbom. - sbom.removeRootComponent(); return new Content( sbom.getAsJsonString().getBytes(StandardCharsets.UTF_8), Api.CYCLONEDX_MEDIA_TYPE); } diff --git a/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_component_sbom.json b/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_component_sbom.json index 733cb95c..18aaef3b 100644 --- a/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_component_sbom.json +++ b/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_component_sbom.json @@ -3,9 +3,23 @@ "specVersion" : "1.4", "version" : 1, "metadata" : { - "timestamp" : "2023-09-28T12:40:41Z" + "timestamp" : "2024-04-29T09:14:16Z", + "component" : { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + } }, "components" : [ + { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + }, { "name" : "anyio", "version" : "3.6.2", @@ -183,6 +197,36 @@ } ], "dependencies" : [ + { + "ref" : "pkg:pypi/default-pip-root@0.0.0", + "dependsOn" : [ + "pkg:pypi/anyio@3.6.2", + "pkg:pypi/asgiref@3.4.1", + "pkg:pypi/beautifulsoup4@4.12.2", + "pkg:pypi/certifi@2023.7.22", + "pkg:pypi/chardet@4.0.0", + "pkg:pypi/contextlib2@21.6.0", + "pkg:pypi/fastapi@0.75.1", + "pkg:pypi/flask@2.0.3", + "pkg:pypi/h11@0.13.0", + "pkg:pypi/idna@2.10", + "pkg:pypi/immutables@0.19", + "pkg:pypi/importlib-metadata@4.8.3", + "pkg:pypi/itsdangerous@2.0.1", + "pkg:pypi/jinja2@3.0.3", + "pkg:pypi/markupsafe@2.0.1", + "pkg:pypi/requests@2.25.1", + "pkg:pypi/six@1.16.0", + "pkg:pypi/sniffio@1.2.0", + "pkg:pypi/soupsieve@2.3.2.post1", + "pkg:pypi/starlette@0.17.1", + "pkg:pypi/typing-extensions@4.1.1", + "pkg:pypi/urllib3@1.26.16", + "pkg:pypi/uvicorn@0.17.0", + "pkg:pypi/werkzeug@2.0.3", + "pkg:pypi/zipp@3.6.0" + ] + }, { "ref" : "pkg:pypi/anyio@3.6.2", "dependsOn" : [ ] diff --git a/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_stack_sbom.json b/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_stack_sbom.json index 35844f5a..b0da8205 100644 --- a/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_stack_sbom.json +++ b/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore/expected_stack_sbom.json @@ -3,9 +3,23 @@ "specVersion" : "1.4", "version" : 1, "metadata" : { - "timestamp" : "2023-09-28T12:40:47Z" + "timestamp" : "2024-04-29T08:54:46Z", + "component" : { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + } }, "components" : [ + { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + }, { "name" : "anyio", "version" : "3.6.2", @@ -183,6 +197,36 @@ } ], "dependencies" : [ + { + "ref" : "pkg:pypi/default-pip-root@0.0.0", + "dependsOn" : [ + "pkg:pypi/anyio@3.6.2", + "pkg:pypi/asgiref@3.4.1", + "pkg:pypi/beautifulsoup4@4.12.2", + "pkg:pypi/certifi@2023.7.22", + "pkg:pypi/chardet@4.0.0", + "pkg:pypi/contextlib2@21.6.0", + "pkg:pypi/fastapi@0.75.1", + "pkg:pypi/flask@2.0.3", + "pkg:pypi/h11@0.13.0", + "pkg:pypi/idna@2.10", + "pkg:pypi/immutables@0.19", + "pkg:pypi/importlib-metadata@4.8.3", + "pkg:pypi/itsdangerous@2.0.1", + "pkg:pypi/jinja2@3.0.3", + "pkg:pypi/markupsafe@2.0.1", + "pkg:pypi/requests@2.25.1", + "pkg:pypi/six@1.16.0", + "pkg:pypi/sniffio@1.2.0", + "pkg:pypi/soupsieve@2.3.2.post1", + "pkg:pypi/starlette@0.17.1", + "pkg:pypi/typing-extensions@4.1.1", + "pkg:pypi/urllib3@1.26.16", + "pkg:pypi/uvicorn@0.17.0", + "pkg:pypi/werkzeug@2.0.3", + "pkg:pypi/zipp@3.6.0" + ] + }, { "ref" : "pkg:pypi/anyio@3.6.2", "dependsOn" : [ diff --git a/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_component_sbom.json b/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_component_sbom.json index 80f1df2b..40a36698 100644 --- a/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_component_sbom.json +++ b/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_component_sbom.json @@ -3,8 +3,23 @@ "specVersion" : "1.4", "version" : 1, "metadata" : { + "timestamp" : "2024-04-29T09:13:11Z", + "component" : { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + } }, "components" : [ + { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + }, { "name" : "anyio", "version" : "3.6.2", @@ -196,6 +211,38 @@ } ], "dependencies" : [ + { + "ref" : "pkg:pypi/default-pip-root@0.0.0", + "dependsOn" : [ + "pkg:pypi/anyio@3.6.2", + "pkg:pypi/asgiref@3.4.1", + "pkg:pypi/beautifulsoup4@4.12.2", + "pkg:pypi/certifi@2023.7.22", + "pkg:pypi/chardet@4.0.0", + "pkg:pypi/click@8.0.4", + "pkg:pypi/contextlib2@21.6.0", + "pkg:pypi/fastapi@0.75.1", + "pkg:pypi/flask@2.0.3", + "pkg:pypi/h11@0.13.0", + "pkg:pypi/idna@2.10", + "pkg:pypi/immutables@0.19", + "pkg:pypi/importlib-metadata@4.8.3", + "pkg:pypi/itsdangerous@2.0.1", + "pkg:pypi/jinja2@3.0.3", + "pkg:pypi/markupsafe@2.0.1", + "pkg:pypi/pydantic@1.9.2", + "pkg:pypi/requests@2.25.1", + "pkg:pypi/six@1.16.0", + "pkg:pypi/sniffio@1.2.0", + "pkg:pypi/soupsieve@2.3.2.post1", + "pkg:pypi/starlette@0.17.1", + "pkg:pypi/typing-extensions@4.1.1", + "pkg:pypi/urllib3@1.26.16", + "pkg:pypi/uvicorn@0.17.0", + "pkg:pypi/werkzeug@2.0.3", + "pkg:pypi/zipp@3.6.0" + ] + }, { "ref" : "pkg:pypi/anyio@3.6.2", "dependsOn" : [ ] diff --git a/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_stack_sbom.json b/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_stack_sbom.json index 6a4bf126..7be3661d 100644 --- a/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_stack_sbom.json +++ b/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore/expected_stack_sbom.json @@ -3,9 +3,23 @@ "specVersion" : "1.4", "version" : 1, "metadata" : { - "timestamp" : "2023-09-28T12:40:44Z" + "timestamp" : "2024-04-29T09:00:11Z", + "component" : { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + } }, "components" : [ + { + "name" : "default-pip-root", + "version" : "0.0.0", + "purl" : "pkg:pypi/default-pip-root@0.0.0", + "type" : "application", + "bom-ref" : "pkg:pypi/default-pip-root@0.0.0" + }, { "name" : "anyio", "version" : "3.6.2", @@ -197,6 +211,38 @@ } ], "dependencies" : [ + { + "ref" : "pkg:pypi/default-pip-root@0.0.0", + "dependsOn" : [ + "pkg:pypi/anyio@3.6.2", + "pkg:pypi/asgiref@3.4.1", + "pkg:pypi/beautifulsoup4@4.12.2", + "pkg:pypi/certifi@2023.7.22", + "pkg:pypi/chardet@4.0.0", + "pkg:pypi/click@8.0.4", + "pkg:pypi/contextlib2@21.6.0", + "pkg:pypi/fastapi@0.75.1", + "pkg:pypi/flask@2.0.3", + "pkg:pypi/h11@0.13.0", + "pkg:pypi/idna@2.10", + "pkg:pypi/immutables@0.19", + "pkg:pypi/importlib-metadata@4.8.3", + "pkg:pypi/itsdangerous@2.0.1", + "pkg:pypi/jinja2@3.0.3", + "pkg:pypi/markupsafe@2.0.1", + "pkg:pypi/pydantic@1.9.2", + "pkg:pypi/requests@2.25.1", + "pkg:pypi/six@1.16.0", + "pkg:pypi/sniffio@1.2.0", + "pkg:pypi/soupsieve@2.3.2.post1", + "pkg:pypi/starlette@0.17.1", + "pkg:pypi/typing-extensions@4.1.1", + "pkg:pypi/urllib3@1.26.16", + "pkg:pypi/uvicorn@0.17.0", + "pkg:pypi/werkzeug@2.0.3", + "pkg:pypi/zipp@3.6.0" + ] + }, { "ref" : "pkg:pypi/anyio@3.6.2", "dependsOn" : [