diff --git a/.github/workflows/build-base-images-sbom-script-image.yml b/.github/workflows/build-sbom-utility-scripts-image.yml similarity index 65% rename from .github/workflows/build-base-images-sbom-script-image.yml rename to .github/workflows/build-sbom-utility-scripts-image.yml index 6094543..7f16a4d 100644 --- a/.github/workflows/build-base-images-sbom-script-image.yml +++ b/.github/workflows/build-sbom-utility-scripts-image.yml @@ -1,22 +1,22 @@ -name: Build base images sbom script image +name: Build sbom utility scripts image on: push: branches: - main paths: - - base-images-sbom-script/** + - sbom-utility-scripts/** pull_request: branches: - main paths: - - base-images-sbom-script/** + - sbom-utility-scripts/** env: REGISTRY: quay.io/redhat-appstudio - IMAGE_NAME: base-images-sbom-script + IMAGE_NAME: sbom-utility-scripts-image jobs: build: @@ -31,10 +31,16 @@ jobs: with: python-version: 3.11 - - name: Run tox checks + - name: Run tox checks for base-images-sbom-script run: | python3 -m pip install tox - cd ./base-images-sbom-script/app/ + cd ./sbom-utility-scripts/scripts/base-images-sbom-script/app/ + tox + + - name: Run tox checks for merge-cachi2-sboms-script + run: | + python3 -m pip install tox + cd ./sbom-utility-scripts/scripts/merge-cachi2-sboms-script/ tox - name: Build Image @@ -43,9 +49,9 @@ jobs: with: image: ${{ env.IMAGE_NAME }} tags: ${{ github.sha }} - context: ./base-images-sbom-script + context: ./sbom-utility-scripts containerfiles: | - ./base-images-sbom-script/Dockerfile + ./sbom-utility-scripts/Dockerfile - name: Push to Quay if: github.event_name == 'push' # don't push image from PR diff --git a/base-images-sbom-script/Dockerfile b/base-images-sbom-script/Dockerfile deleted file mode 100644 index ace75d1..0000000 --- a/base-images-sbom-script/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM registry.access.redhat.com/ubi9/python-39:1-192.1722518946@sha256:0176b477075984d5a502253f951d2502f0763c551275f9585ac515b9f241d73d - -WORKDIR /app -COPY app/requirements.txt /app -COPY app/base_images_sbom_script.py /app - -RUN pip3 install -r requirements.txt diff --git a/sbom-utility-scripts/Dockerfile b/sbom-utility-scripts/Dockerfile new file mode 100644 index 0000000..1c92d24 --- /dev/null +++ b/sbom-utility-scripts/Dockerfile @@ -0,0 +1,11 @@ +FROM registry.access.redhat.com/ubi9/python-39:1-192.1722518946@sha256:0176b477075984d5a502253f951d2502f0763c551275f9585ac515b9f241d73d + +WORKDIR /scripts + +COPY scripts/merge_syft_sboms.py /scripts +COPY scripts/merge-cachi2-sboms-script/merge_cachi2_sboms.py /scripts +COPY scripts/create_purl_sbom.py /scripts +COPY scripts/base-images-sbom-script/app/base_images_sbom_script.py /scripts +COPY scripts/base-images-sbom-script/app/requirements.txt /scripts + +RUN pip3 install -r requirements.txt diff --git a/base-images-sbom-script/README.md b/sbom-utility-scripts/scripts/base-images-sbom-script/README.md similarity index 100% rename from base-images-sbom-script/README.md rename to sbom-utility-scripts/scripts/base-images-sbom-script/README.md diff --git a/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py similarity index 100% rename from base-images-sbom-script/app/base_images_sbom_script.py rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py diff --git a/base-images-sbom-script/app/requirements-test.in b/sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements-test.in similarity index 100% rename from base-images-sbom-script/app/requirements-test.in rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements-test.in diff --git a/base-images-sbom-script/app/requirements-test.txt b/sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements-test.txt similarity index 100% rename from base-images-sbom-script/app/requirements-test.txt rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements-test.txt diff --git a/base-images-sbom-script/app/requirements.in b/sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements.in similarity index 100% rename from base-images-sbom-script/app/requirements.in rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements.in diff --git a/base-images-sbom-script/app/requirements.txt b/sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements.txt similarity index 100% rename from base-images-sbom-script/app/requirements.txt rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/requirements.txt diff --git a/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py similarity index 100% rename from base-images-sbom-script/app/test_base_images_sbom_script.py rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py diff --git a/base-images-sbom-script/app/tox.ini b/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini similarity index 100% rename from base-images-sbom-script/app/tox.ini rename to sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini diff --git a/sbom-utility-scripts/scripts/create_purl_sbom.py b/sbom-utility-scripts/scripts/create_purl_sbom.py new file mode 100644 index 0000000..bb22db5 --- /dev/null +++ b/sbom-utility-scripts/scripts/create_purl_sbom.py @@ -0,0 +1,10 @@ +import json + +with open("./sbom-cyclonedx.json") as f: + cyclonedx_sbom = json.load(f) + +purls = [{"purl": component["purl"]} for component in cyclonedx_sbom.get("components", []) if "purl" in component] +purl_content = {"image_contents": {"dependencies": purls}} + +with open("sbom-purl.json", "w") as output_file: + json.dump(purl_content, output_file, indent=4) diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/merge_cachi2_sboms.py b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/merge_cachi2_sboms.py new file mode 100644 index 0000000..3473862 --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/merge_cachi2_sboms.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +import json +from argparse import ArgumentParser +from typing import Any, Callable +from urllib.parse import quote_plus, urlsplit + + +def _is_syft_local_golang_component(component: dict) -> bool: + """ + Check if a Syft Golang reported component is a local replacement. + + Local replacements are reported in a very different way by Cachi2, which is why the same + reports by Syft should be removed. + """ + return component.get("purl", "").startswith("pkg:golang") and ( + component.get("name", "").startswith(".") or component.get("version", "") == "(devel)" + ) + + +def _is_cachi2_non_registry_dependency(component: dict) -> bool: + """ + Check if Cachi2 component was fetched from a VCS or a direct file location. + + Cachi2 reports non-registry components in a different way from Syft, so the reports from + Syft need to be removed. + + Unfortunately, there's no way to determine which components are non-registry by looking + at the Syft report alone. This function is meant to create a list of non-registry components + from Cachi2's SBOM, then remove the corresponding ones reported by Syft for the merged SBOM. + + Note that this function is only applicable for PyPI or NPM components. + """ + purl = component.get("purl", "") + + return (purl.startswith("pkg:pypi") or purl.startswith("pkg:npm")) and ( + "vcs_url=" in purl or "download_url=" in purl + ) + + +def _unique_key_cachi2(component: dict) -> str: + """ + Create a unique key from Cachi2 reported components. + + This is done by taking a purl and removing any qualifiers and subpaths. + + See https://github.com/package-url/purl-spec/tree/master#purl for more info on purls. + """ + url = urlsplit(component["purl"]) + return url.scheme + ":" + url.path + + +def _unique_key_syft(component: dict) -> str: + """ + Create a unique key for Syft reported components. + + This is done by taking a lowercase namespace/name, and URL encoding the version. + + Syft does not set any qualifier for NPM, Pip or Golang, so there's no need to remove them + as done in _unique_key_cachi2. + + If a Syft component lacks a purl (e.g. type OS), we'll use its name and version instead. + """ + if "purl" not in component: + return component.get("name", "") + "@" + component.get("version", "") + + if "@" in component["purl"]: + name, version = component["purl"].split("@") + + if name.startswith("pkg:pypi"): + name = name.lower() + + if name.startswith("pkg:golang"): + version = quote_plus(version) + + return f"{name}@{version}" + else: + return component["purl"] + + +def _get_syft_component_filter(cachi_sbom_components: list[dict[str, Any]]) -> Callable: + """ + Get a function that filters out Syft components for the merged SBOM. + + This function currently considers a Syft component as a duplicate/removable if: + - it has the same key as a Cachi2 component + - it is a local Golang replacement + - is a non-registry component also reported by Cachi2 + + Note that for the last bullet, we can only rely on the Pip dependency's name to find a + duplicate. This is because Cachi2 does not report a non-PyPI Pip dependency's version. + + Even though multiple versions of a same dependency can be available in the same project, + we are removing all Syft instances by name only because Cachi2 will report them correctly, + given that it scans all the source code properly and the image is built hermetically. + """ + cachi2_non_registry_components = [ + component["name"] for component in cachi_sbom_components if _is_cachi2_non_registry_dependency(component) + ] + + cachi2_indexed_components = {_unique_key_cachi2(component): component for component in cachi_sbom_components} + + def is_duplicate_non_registry_component(component: dict[str, Any]) -> bool: + return component["name"] in cachi2_non_registry_components + + def component_is_duplicated(component: dict[str, Any]) -> bool: + key = _unique_key_syft(component) + + return ( + _is_syft_local_golang_component(component) + or is_duplicate_non_registry_component(component) + or key in cachi2_indexed_components.keys() + ) + + return component_is_duplicated + + +def _merge_tools_metadata(syft_sbom: dict[Any, Any], cachi2_sbom: dict[Any, Any]) -> None: + """Merge the content of tools in the metadata section of the SBOM. + + With CycloneDX 1.5, a new format for specifying tools was introduced, and the format from 1.4 + was marked as deprecated. + + This function aims to support both formats in the Syft SBOM. We're assuming the Cachi2 SBOM + was generated with the same version as this script, and it will be in the older format. + """ + syft_tools = syft_sbom["metadata"]["tools"] + cachi2_tools = cachi2_sbom["metadata"]["tools"] + + if isinstance(syft_tools, dict): + components = [] + + for t in cachi2_tools: + components.append( + { + "author": t["vendor"], + "name": t["name"], + "type": "application", + } + ) + + syft_tools["components"].extend(components) + elif isinstance(syft_tools, list): + syft_tools.extend(cachi2_tools) + else: + raise RuntimeError( + "The .metadata.tools JSON key is in an unexpected format. " + f"Expected dict or list, got {type(syft_tools)}." + ) + + +def merge_sboms(cachi2_sbom_path: str, syft_sbom_path: str) -> str: + """Merge Cachi2 components into the Syft SBOM while removing duplicates.""" + with open(cachi2_sbom_path) as file: + cachi2_sbom = json.load(file) + + with open(syft_sbom_path) as file: + syft_sbom = json.load(file) + + is_duplicate_component = _get_syft_component_filter(cachi2_sbom["components"]) + + filtered_syft_components = [c for c in syft_sbom.get("components", []) if not is_duplicate_component(c)] + + syft_sbom["components"] = filtered_syft_components + cachi2_sbom["components"] + + _merge_tools_metadata(syft_sbom, cachi2_sbom) + + return json.dumps(syft_sbom, indent=2) + + +if __name__ == "__main__": + parser = ArgumentParser() + + parser.add_argument("cachi2_sbom_path") + parser.add_argument("syft_sbom_path") + + args = parser.parse_args() + + merged_sbom = merge_sboms(args.cachi2_sbom_path, args.syft_sbom_path) + + print(merged_sbom) diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/requirements-test.in b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/requirements-test.in new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/requirements-test.in @@ -0,0 +1 @@ +pytest diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/requirements-test.txt b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/requirements-test.txt new file mode 100644 index 0000000..18bd5a4 --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/requirements-test.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes --output-file=requirements-test.txt requirements-test.in +# +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 + # via pytest +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 + # via pytest +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via pytest +pytest==8.3.2 \ + --hash=sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5 \ + --hash=sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce + # via -r requirements-test.in diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/cachi2.bom.json b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/cachi2.bom.json new file mode 100644 index 0000000..846f639 --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/cachi2.bom.json @@ -0,0 +1,227 @@ +{ + "bomFormat": "CycloneDX", + "components": [ + { + "name": "aiowsgi", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "0.8", + "purl": "pkg:pypi/aiowsgi@0.8", + "type": "library" + }, + { + "name": "appr", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:pypi/appr?checksum=sha256:ee6a0a38bed8cff46a562ed3620bc453141a02262ab0c8dd055824af2829ee5c&download_url=https://github.com/quay/appr/archive/37ff9a487a54ad41b59855ecd76ee092fe206a84.zip", + "type": "library" + }, + { + "name": "archive/tar", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:golang/archive/tar?type=package", + "type": "library" + }, + { + "name": "cachi2", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "0.0.1", + "purl": "pkg:pypi/cachi2@0.0.1?vcs_url=git%2Bssh://git%40github.com/containerbuildsystem/cachi2%40fc0d6079c2dc9b2a491c0848e550ad3509986110", + "type": "library" + }, + { + "name": "cachito-npm-without-deps", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cachito-npm-without-deps?vcs_url=git%2Bhttps://github.com/cachito-testing/cachito-npm-without-deps.git%402f0ce1d7b1f8b35572d919428b965285a69583f6", + "type": "library" + }, + { + "name": "code.gitea.io/sdk/gitea", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.15.1", + "purl": "pkg:golang/code.gitea.io/sdk/gitea@v0.15.1?type=module", + "type": "library" + }, + { + "name": "code.gitea.io/sdk/gitea", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.15.1", + "purl": "pkg:golang/code.gitea.io/sdk/gitea@v0.15.1?type=package", + "type": "library" + }, + { + "name": "fecha", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/fecha?checksum=sha512:8ae71e98d68e38e1f6e4c629187684dd85e4dc96647c7219b1dd189598ea52865e947f0ad94a7001fa8fb5eccf58467fe34ad10066e831af3374120134604bd5&download_url=https://github.com/taylorhakes/fecha/archive/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz", + "type": "library" + }, + { + "name": "github.com/docker/cli/cli/config", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v23.0.0-rc.3+incompatible", + "purl": "pkg:golang/github.com/docker/cli/cli/config@v23.0.0-rc.3%2Bincompatible?type=package", + "type": "library" + }, + { + "name": "github.com/docker/cli", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v23.0.0-rc.3+incompatible", + "purl": "pkg:golang/github.com/docker/cli@v23.0.0-rc.3%2Bincompatible?type=module", + "type": "library" + }, + { + "name": "knative.dev/pkg/metrics", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230125083639-408ad0773f47", + "purl": "pkg:golang/knative.dev/pkg/metrics@v0.0.0-20230125083639-408ad0773f47?type=package", + "type": "library" + }, + { + "name": "knative.dev/pkg", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230125083639-408ad0773f47", + "purl": "pkg:golang/knative.dev/pkg@v0.0.0-20230125083639-408ad0773f47?type=module", + "type": "library" + }, + { + "name": "github.com/redhat-appstudio/build-service", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230503110830-d1a9e858489d", + "purl": "pkg:golang/github.com/redhat-appstudio/build-service@v0.0.0-20230503110830-d1a9e858489d?type=module", + "type": "library" + }, + { + "name": "github.com/redhat-appstudio/build-service", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230503110830-d1a9e858489d", + "purl": "pkg:golang/github.com/redhat-appstudio/build-service@v0.0.0-20230503110830-d1a9e858489d?type=package", + "type": "library" + }, + { + "name": "github.com/cachito-testing/gomod-pandemonium/terminaltor", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v1.0.0", + "purl": "pkg:golang/github.com/cachito-testing/gomod-pandemonium/terminaltor@v1.0.0?type=module", + "type": "library" + }, + { + "name": "github.com/cachito-testing/gomod-pandemonium/terminaltor", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v1.0.0", + "purl": "pkg:golang/github.com/cachito-testing/gomod-pandemonium/terminaltor@v1.0.0?type=package", + "type": "library" + }, + { + "name": "PyYAML", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "6.0", + "purl": "pkg:pypi/pyyaml@6.0", + "type": "library" + }, + { + "name": "test_package_cachi2", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "1.0.0", + "purl": "pkg:pypi/test-package-cachi2@1.0.0?vcs_url=git%2Bssh://git%40github.com/brunoapimentel/pip-e2e-test.git%40294df352deed835cf703ae8a799926418ae5fd3b", + "type": "library" + } + ], + "metadata": { + "tools": [ + { + "vendor": "red hat", + "name": "cachi2" + } + ] + }, + "specVersion": "1.4", + "version": 1 +} diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/merged.bom.json b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/merged.bom.json new file mode 100644 index 0000000..dc876ce --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/merged.bom.json @@ -0,0 +1,327 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:4370d1ba-7643-4579-8313-bc715da2fa90", + "version": 1, + "metadata": { + "timestamp": "2023-05-03T18:19:41Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "0.100.0" + }, + { + "type": "application", + "author": "red hat", + "name": "cachi2" + } + ] + }, + "component": { + "bom-ref": "6b8edfe5f2756e0", + "type": "file", + "name": "/var/lib/containers/storage/vfs/dir/517aef0ffe20db360d19aa475dbbfbe03f452f53403881a31f9a475c83af788b" + } + }, + "components": [ + { + "bom-ref": "pkg:rpm/rhel/bash@4.4.20-4.el8_6?arch=x86_64&upstream=bash-4.4.20-4.el8_6.src.rpm&distro=rhel-8.7&package-id=5b17560161ffa050", + "type": "library", + "publisher": "Red Hat, Inc.", + "name": "bash", + "version": "4.4.20-4.el8_6", + "cpe": "cpe:2.3:a:redhat:bash:4.4.20-4.el8_6:*:*:*:*:*:*:*", + "purl": "pkg:rpm/rhel/bash@4.4.20-4.el8_6?arch=x86_64&upstream=bash-4.4.20-4.el8_6.src.rpm&distro=rhel-8.7", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "rpmdb-cataloger" + }, + { + "name": "syft:package:metadataType", + "value": "RpmdbMetadata" + }, + { + "name": "syft:package:type", + "value": "rpm" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:bash:bash:4.4.20-4.el8_6:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "var/lib/rpm/Packages" + }, + { + "name": "syft:metadata:release", + "value": "4.el8_6" + }, + { + "name": "syft:metadata:size", + "value": "6861444" + }, + { + "name": "syft:metadata:sourceRpm", + "value": "bash-4.4.20-4.el8_6.src.rpm" + } + ] + }, + { + "type": "operating-system", + "name": "rhel", + "version": "8.7", + "description": "Red Hat Enterprise Linux 8.7 (Ootpa)", + "cpe": "cpe:/o:redhat:enterprise_linux:8::baseos", + "swid": { + "tagId": "rhel", + "name": "rhel", + "version": "8.7" + }, + "externalReferences": [ + { + "url": "https://bugzilla.redhat.com/", + "type": "issue-tracker" + }, + { + "url": "https://www.redhat.com/", + "type": "website" + } + ], + "properties": [ + { + "name": "syft:distro:id", + "value": "rhel" + }, + { + "name": "syft:distro:idLike:0", + "value": "fedora" + }, + { + "name": "syft:distro:prettyName", + "value": "Red Hat Enterprise Linux 8.7 (Ootpa)" + }, + { + "name": "syft:distro:versionID", + "value": "8.7" + } + ] + }, + { + "name": "aiowsgi", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "0.8", + "purl": "pkg:pypi/aiowsgi@0.8", + "type": "library" + }, + { + "name": "appr", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:pypi/appr?checksum=sha256:ee6a0a38bed8cff46a562ed3620bc453141a02262ab0c8dd055824af2829ee5c&download_url=https://github.com/quay/appr/archive/37ff9a487a54ad41b59855ecd76ee092fe206a84.zip", + "type": "library" + }, + { + "name": "archive/tar", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:golang/archive/tar?type=package", + "type": "library" + }, + { + "name": "cachi2", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "0.0.1", + "purl": "pkg:pypi/cachi2@0.0.1?vcs_url=git%2Bssh://git%40github.com/containerbuildsystem/cachi2%40fc0d6079c2dc9b2a491c0848e550ad3509986110", + "type": "library" + }, + { + "name": "cachito-npm-without-deps", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cachito-npm-without-deps?vcs_url=git%2Bhttps://github.com/cachito-testing/cachito-npm-without-deps.git%402f0ce1d7b1f8b35572d919428b965285a69583f6", + "type": "library" + }, + { + "name": "code.gitea.io/sdk/gitea", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.15.1", + "purl": "pkg:golang/code.gitea.io/sdk/gitea@v0.15.1?type=module", + "type": "library" + }, + { + "name": "code.gitea.io/sdk/gitea", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.15.1", + "purl": "pkg:golang/code.gitea.io/sdk/gitea@v0.15.1?type=package", + "type": "library" + }, + { + "name": "fecha", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/fecha?checksum=sha512:8ae71e98d68e38e1f6e4c629187684dd85e4dc96647c7219b1dd189598ea52865e947f0ad94a7001fa8fb5eccf58467fe34ad10066e831af3374120134604bd5&download_url=https://github.com/taylorhakes/fecha/archive/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz", + "type": "library" + }, + { + "name": "github.com/docker/cli/cli/config", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v23.0.0-rc.3+incompatible", + "purl": "pkg:golang/github.com/docker/cli/cli/config@v23.0.0-rc.3%2Bincompatible?type=package", + "type": "library" + }, + { + "name": "github.com/docker/cli", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v23.0.0-rc.3+incompatible", + "purl": "pkg:golang/github.com/docker/cli@v23.0.0-rc.3%2Bincompatible?type=module", + "type": "library" + }, + { + "name": "knative.dev/pkg/metrics", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230125083639-408ad0773f47", + "purl": "pkg:golang/knative.dev/pkg/metrics@v0.0.0-20230125083639-408ad0773f47?type=package", + "type": "library" + }, + { + "name": "knative.dev/pkg", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230125083639-408ad0773f47", + "purl": "pkg:golang/knative.dev/pkg@v0.0.0-20230125083639-408ad0773f47?type=module", + "type": "library" + }, + { + "name": "github.com/redhat-appstudio/build-service", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230503110830-d1a9e858489d", + "purl": "pkg:golang/github.com/redhat-appstudio/build-service@v0.0.0-20230503110830-d1a9e858489d?type=module", + "type": "library" + }, + { + "name": "github.com/redhat-appstudio/build-service", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v0.0.0-20230503110830-d1a9e858489d", + "purl": "pkg:golang/github.com/redhat-appstudio/build-service@v0.0.0-20230503110830-d1a9e858489d?type=package", + "type": "library" + }, + { + "name": "github.com/cachito-testing/gomod-pandemonium/terminaltor", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v1.0.0", + "purl": "pkg:golang/github.com/cachito-testing/gomod-pandemonium/terminaltor@v1.0.0?type=module", + "type": "library" + }, + { + "name": "github.com/cachito-testing/gomod-pandemonium/terminaltor", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "v1.0.0", + "purl": "pkg:golang/github.com/cachito-testing/gomod-pandemonium/terminaltor@v1.0.0?type=package", + "type": "library" + }, + { + "name": "PyYAML", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "6.0", + "purl": "pkg:pypi/pyyaml@6.0", + "type": "library" + }, + { + "name": "test_package_cachi2", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "version": "1.0.0", + "purl": "pkg:pypi/test-package-cachi2@1.0.0?vcs_url=git%2Bssh://git%40github.com/brunoapimentel/pip-e2e-test.git%40294df352deed835cf703ae8a799926418ae5fd3b", + "type": "library" + } + ] +} diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/syft.bom.json b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/syft.bom.json new file mode 100644 index 0000000..6bf35bc --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_data/syft.bom.json @@ -0,0 +1,903 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:4370d1ba-7643-4579-8313-bc715da2fa90", + "version": 1, + "metadata": { + "timestamp": "2023-05-03T18:19:41Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "0.100.0" + } + ] + }, + "component": { + "bom-ref": "6b8edfe5f2756e0", + "type": "file", + "name": "/var/lib/containers/storage/vfs/dir/517aef0ffe20db360d19aa475dbbfbe03f452f53403881a31f9a475c83af788b" + } + }, + "components": [ + { + "bom-ref": "pkg:pypi/aiowsgi@0.8?package-id=3038521054a801b2", + "type": "library", + "author": "Gael Pasgrimaud ", + "name": "aiowsgi", + "version": "0.8", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "cpe": "cpe:2.3:a:gael_pasgrimaud:python-aiowsgi:0.8:*:*:*:*:*:*:*", + "purl": "pkg:pypi/aiowsgi@0.8", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "PythonPackageMetadata" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:gael_pasgrimaud:python_aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-aiowsgi:python-aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-aiowsgi:python_aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_aiowsgi:python-aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_aiowsgi:python_aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:gael_pasgrimaud:aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:aiowsgi:python-aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:aiowsgi:python_aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-aiowsgi:aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_aiowsgi:aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python-aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python_aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:gael:python-aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:gael:python_aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:aiowsgi:aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:gael:aiowsgi:0.8:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "opt/app-root/lib/python3.9/site-packages/aiowsgi-0.8.dist-info/METADATA" + }, + { + "name": "syft:location:1:path", + "value": "opt/app-root/lib/python3.9/site-packages/aiowsgi-0.8.dist-info/METADATA" + }, + { + "name": "syft:location:2:path", + "value": "opt/app-root/lib/python3.9/site-packages/aiowsgi-0.8.dist-info/RECORD" + }, + { + "name": "syft:location:3:path", + "value": "opt/app-root/lib/python3.9/site-packages/aiowsgi-0.8.dist-info/top_level.txt" + } + ] + }, + { + "bom-ref": "pkg:pypi/appr@0.7.4?package-id=86c12ef7e3e40483", + "type": "library", + "author": "Antoine Legrand <2t.antoine@gmail.com>", + "name": "appr", + "version": "0.7.4", + "cpe": "cpe:2.3:a:antoine_legrand:python-appr:0.7.4:*:*:*:*:*:*:*", + "purl": "pkg:pypi/appr@0.7.4", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "PythonPackageMetadata" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:antoine_legrand:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-appr:python-appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-appr:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_appr:python-appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_appr:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:2t-antoine:python-appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:2t-antoine:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:2t_antoine:python-appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:2t_antoine:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:antoine_legrand:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python-appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:appr:python-appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:appr:python_appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-appr:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_appr:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:2t-antoine:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:2t_antoine:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:appr:appr:0.7.4:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "opt/app-root/lib/python3.9/site-packages/appr-0.7.4-py3.9.egg-info/PKG-INFO" + }, + { + "name": "syft:location:1:path", + "value": "opt/app-root/lib/python3.9/site-packages/appr-0.7.4-py3.9.egg-info/PKG-INFO" + }, + { + "name": "syft:location:2:path", + "value": "opt/app-root/lib/python3.9/site-packages/appr-0.7.4-py3.9.egg-info/top_level.txt" + } + ] + }, + { + "bom-ref": "pkg:rpm/rhel/bash@4.4.20-4.el8_6?arch=x86_64&upstream=bash-4.4.20-4.el8_6.src.rpm&distro=rhel-8.7&package-id=5b17560161ffa050", + "type": "library", + "publisher": "Red Hat, Inc.", + "name": "bash", + "version": "4.4.20-4.el8_6", + "cpe": "cpe:2.3:a:redhat:bash:4.4.20-4.el8_6:*:*:*:*:*:*:*", + "purl": "pkg:rpm/rhel/bash@4.4.20-4.el8_6?arch=x86_64&upstream=bash-4.4.20-4.el8_6.src.rpm&distro=rhel-8.7", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "rpmdb-cataloger" + }, + { + "name": "syft:package:metadataType", + "value": "RpmdbMetadata" + }, + { + "name": "syft:package:type", + "value": "rpm" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:bash:bash:4.4.20-4.el8_6:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "var/lib/rpm/Packages" + }, + { + "name": "syft:metadata:release", + "value": "4.el8_6" + }, + { + "name": "syft:metadata:size", + "value": "6861444" + }, + { + "name": "syft:metadata:sourceRpm", + "value": "bash-4.4.20-4.el8_6.src.rpm" + } + ] + }, + { + "bom-ref": "pkg:pypi/cachi2@0.0.post1+gdfd2180.d20230704?package-id=d6b54ce4d7c02efb", + "type": "library", + "name": "cachi2", + "version": "0.0.post1+gdfd2180.d20230704", + "cpe": "cpe:2.3:a:python-cachi2:python-cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*", + "purl": "pkg:pypi/cachi2@0.0.post1+gdfd2180.d20230704", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "PythonPackageMetadata" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-cachi2:python_cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_cachi2:python-cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_cachi2:python_cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachi2:python-cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachi2:python_cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-cachi2:cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python-cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python_cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_cachi2:cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachi2:cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:cachi2:0.0.post1\\+gdfd2180.d20230704:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "src/cachi2.egg-info/PKG-INFO" + }, + { + "name": "syft:location:1:path", + "value": "src/cachi2.egg-info/top_level.txt" + } + ] + }, + { + "bom-ref": "pkg:npm/cachito-npm-without-deps%40git+https:/github.com/cachito-testing/cachito-npm-without-deps.git%232f0ce1d7b1f8b35572d919428b965285a69583f6?package-id=f381e97e072d545c", + "type": "library", + "name": "cachito-npm-without-deps", + "version": "git+https://github.com/cachito-testing/cachito-npm-without-deps.git#2f0ce1d7b1f8b35572d919428b965285a69583f6", + "cpe": "cpe:2.3:a:cachito-npm-without-deps:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*", + "purl": "pkg:npm/cachito-npm-without-deps@git+https://github.com/cachito-testing/cachito-npm-without-deps.git%232f0ce1d7b1f8b35572d919428b965285a69583f6", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito-npm-without-deps:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito_npm_without_deps:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito_npm_without_deps:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito-npm-without:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito-npm-without:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito_npm_without:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito_npm_without:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito-npm:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito-npm:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito_npm:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito_npm:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:cachito:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:*:cachito-npm-without-deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:*:cachito_npm_without_deps:git\\+https\\:\\/\\/github.com\\/cachito-testing\\/cachito-npm-without-deps.git\\#2f0ce1d7b1f8b35572d919428b965285a69583f6:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "opt/app-root/src/package-lock.json" + } + ] + }, + { + "bom-ref": "pkg:golang/code.gitea.io/sdk/gitea@v0.15.1?package-id=bd0f7d1b7175ede9", + "type": "library", + "name": "code.gitea.io/sdk/gitea", + "version": "v0.15.1", + "cpe": "cpe:2.3:a:sdk:gitea:v0.15.1:*:*:*:*:*:*:*", + "purl": "pkg:golang/code.gitea.io/sdk/gitea@v0.15.1", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "go-module-binary-cataloger" + }, + { + "name": "syft:package:language", + "value": "go" + }, + { + "name": "syft:package:metadataType", + "value": "GolangBinMetadata" + }, + { + "name": "syft:package:type", + "value": "go-module" + }, + { + "name": "syft:location:0:path", + "value": "manager" + }, + { + "name": "syft:metadata:architecture", + "value": "amd64" + }, + { + "name": "syft:metadata:goCompiledVersion", + "value": "go1.18.4" + }, + { + "name": "syft:metadata:h1Digest", + "value": "h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=" + } + ] + }, + { + "bom-ref": "pkg:npm/fecha%40https:/github.com/taylorhakes/fecha/archive/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz?package-id=b989ad78c681a9ab", + "type": "library", + "name": "fecha", + "version": "https://github.com/taylorhakes/fecha/archive/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz", + "cpe": "cpe:2.3:a:fecha:fecha:https\\:\\/\\/github.com\\/taylorhakes\\/fecha\\/archive\\/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz:*:*:*:*:*:*:*", + "purl": "pkg:npm/fecha@https://github.com/taylorhakes/fecha/archive/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:*:fecha:https\\:\\/\\/github.com\\/taylorhakes\\/fecha\\/archive\\/91680e4db1415fea33eac878cfd889c80a7b55c7.tar.gz:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "opt/app-root/src/package-lock.json" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/docker/cli@v23.0.0-rc.3+incompatible?package-id=53a1b0df25cf6062", + "type": "library", + "name": "github.com/docker/cli", + "version": "v23.0.0-rc.3+incompatible", + "cpe": "cpe:2.3:a:docker:cli:v23.0.0-rc.3\\+incompatible:*:*:*:*:*:*:*", + "purl": "pkg:golang/github.com/docker/cli@v23.0.0-rc.3+incompatible", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "go-module-binary-cataloger" + }, + { + "name": "syft:package:language", + "value": "go" + }, + { + "name": "syft:package:metadataType", + "value": "GolangBinMetadata" + }, + { + "name": "syft:package:type", + "value": "go-module" + }, + { + "name": "syft:location:0:path", + "value": "manager" + }, + { + "name": "syft:metadata:architecture", + "value": "amd64" + }, + { + "name": "syft:metadata:goCompiledVersion", + "value": "go1.18.4" + }, + { + "name": "syft:metadata:h1Digest", + "value": "h1:OPrcUDrpApVrVZsZByISt51zID7HT0VxDKa/onvUzOo=" + } + ] + }, + { + "bom-ref": "pkg:golang/knative.dev/pkg@v0.0.0-20230125083639-408ad0773f47?package-id=d90b5a02ef9f5ceb", + "type": "library", + "name": "knative.dev/pkg", + "version": "v0.0.0-20230125083639-408ad0773f47", + "purl": "pkg:golang/knative.dev/pkg@v0.0.0-20230125083639-408ad0773f47", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "go-module-binary-cataloger" + }, + { + "name": "syft:package:language", + "value": "go" + }, + { + "name": "syft:package:metadataType", + "value": "GolangBinMetadata" + }, + { + "name": "syft:package:type", + "value": "go-module" + }, + { + "name": "syft:location:0:path", + "value": "manager" + }, + { + "name": "syft:metadata:architecture", + "value": "amd64" + }, + { + "name": "syft:metadata:goCompiledVersion", + "value": "go1.18.4" + }, + { + "name": "syft:metadata:h1Digest", + "value": "h1:zlRO7wXOHVYgKvsC3nIaYGqeQGlLJL8EIUY30Rh37Is=" + } + ] + }, + { + "bom-ref": "pkg:golang/./terminaltor?package-id=ead05afcd869908f", + "type": "library", + "name": "./terminaltor", + "purl": "pkg:golang/./terminaltor", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "go-mod-file-cataloger" + }, + { + "name": "syft:package:language", + "value": "go" + }, + { + "name": "syft:package:type", + "value": "go-module" + }, + { + "name": "syft:location:0:path", + "value": "opt/app-root/src/go.mod" + } + ] + }, + { + "bom-ref": "pkg:golang/./terminaltor@(devel)?package-id=f75768aa294abccc", + "type": "library", + "name": "./terminaltor", + "version": "(devel)", + "purl": "pkg:golang/./terminaltor@(devel)", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "go-module-binary-cataloger" + }, + { + "name": "syft:package:language", + "value": "go" + }, + { + "name": "syft:package:metadataType", + "value": "GolangBinMetadata" + }, + { + "name": "syft:package:type", + "value": "go-module" + }, + { + "name": "syft:location:0:path", + "value": "opt/app-root/src/main" + }, + { + "name": "syft:metadata:architecture", + "value": "amd64" + }, + { + "name": "syft:metadata:goCompiledVersion", + "value": "go1.18.9" + }, + { + "name": "syft:metadata:mainModule", + "value": "github.com/cachito-testing/gomod-pandemonium" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/redhat-appstudio/build-service@(devel)?package-id=2d30ce4decf63ac", + "type": "library", + "name": "github.com/redhat-appstudio/build-service", + "version": "(devel)", + "cpe": "cpe:2.3:a:redhat-appstudio:build-service:\\(devel\\):*:*:*:*:*:*:*", + "purl": "pkg:golang/github.com/redhat-appstudio/build-service@(devel)", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "go-module-binary-cataloger" + }, + { + "name": "syft:package:language", + "value": "go" + }, + { + "name": "syft:package:metadataType", + "value": "GolangBinMetadata" + }, + { + "name": "syft:package:type", + "value": "go-module" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:redhat-appstudio:build_service:\\(devel\\):*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:redhat_appstudio:build-service:\\(devel\\):*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:redhat_appstudio:build_service:\\(devel\\):*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:redhat:build-service:\\(devel\\):*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:redhat:build_service:\\(devel\\):*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "manager" + }, + { + "name": "syft:metadata:architecture", + "value": "amd64" + }, + { + "name": "syft:metadata:goCompiledVersion", + "value": "go1.18.4" + } + ] + }, + { + "bom-ref": "pkg:pypi/pyyaml@6.0?package-id=377756039bc8cd3d", + "type": "library", + "author": "Kirill Simonov ", + "name": "PyYAML", + "version": "6.0", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "cpe": "cpe:2.3:a:kirill_simonov:python-PyYAML:6.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/PyYAML@6.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "PythonPackageMetadata" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:kirill_simonov:python_PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-PyYAML:python-PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-PyYAML:python_PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_PyYAML:python-PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_PyYAML:python_PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:kirill_simonov:PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:PyYAML:python-PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:PyYAML:python_PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-PyYAML:PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python-PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python_PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_PyYAML:PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:xi:python-PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:xi:python_PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:PyYAML:PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:xi:PyYAML:6.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "usr/local/lib64/python3.11/site-packages/PyYAML-6.0.dist-info/METADATA" + }, + { + "name": "syft:location:1:path", + "value": "usr/local/lib64/python3.11/site-packages/PyYAML-6.0.dist-info/RECORD" + }, + { + "name": "syft:location:2:path", + "value": "usr/local/lib64/python3.11/site-packages/PyYAML-6.0.dist-info/top_level.txt" + } + ] + }, + { + "type": "operating-system", + "name": "rhel", + "version": "8.7", + "description": "Red Hat Enterprise Linux 8.7 (Ootpa)", + "cpe": "cpe:/o:redhat:enterprise_linux:8::baseos", + "swid": { + "tagId": "rhel", + "name": "rhel", + "version": "8.7" + }, + "externalReferences": [ + { + "url": "https://bugzilla.redhat.com/", + "type": "issue-tracker" + }, + { + "url": "https://www.redhat.com/", + "type": "website" + } + ], + "properties": [ + { + "name": "syft:distro:id", + "value": "rhel" + }, + { + "name": "syft:distro:idLike:0", + "value": "fedora" + }, + { + "name": "syft:distro:prettyName", + "value": "Red Hat Enterprise Linux 8.7 (Ootpa)" + }, + { + "name": "syft:distro:versionID", + "value": "8.7" + } + ] + } + ] +} diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_merge_cachi2_sboms.py b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_merge_cachi2_sboms.py new file mode 100644 index 0000000..b88c945 --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/test_merge_cachi2_sboms.py @@ -0,0 +1,133 @@ +import json +from pathlib import Path +from typing import Any + +import pytest + +from merge_cachi2_sboms import merge_sboms + +TOOLS_METADATA = { + "syft-cyclonedx-1.4": { + "name": "syft", + "vendor": "anchore", + "version": "0.47.0", + }, + "syft-cyclonedx-1.5": { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "0.100.0", + }, + "cachi2-cyclonedx-1.4": { + "name": "cachi2", + "vendor": "red hat", + }, + "cachi2-cyclonedx-1.5": { + "type": "application", + "author": "red hat", + "name": "cachi2", + }, +} + + +@pytest.fixture +def data_dir() -> Path: + """Path to the directory for storing unit test data.""" + return Path(__file__).parent / "test_data" + + +def test_merge_sboms(data_dir: Path) -> None: + result = merge_sboms(f"{data_dir}/cachi2.bom.json", f"{data_dir}/syft.bom.json") + + with open(f"{data_dir}/merged.bom.json") as file: + expected_sbom = json.load(file) + + assert json.loads(result) == expected_sbom + + +@pytest.mark.parametrize( + "syft_tools_metadata, expected_result", + [ + ( + [TOOLS_METADATA["syft-cyclonedx-1.4"]], + [ + TOOLS_METADATA["syft-cyclonedx-1.4"], + TOOLS_METADATA["cachi2-cyclonedx-1.4"], + ], + ), + ( + { + "components": [TOOLS_METADATA["syft-cyclonedx-1.5"]], + }, + { + "components": [ + TOOLS_METADATA["syft-cyclonedx-1.5"], + TOOLS_METADATA["cachi2-cyclonedx-1.5"], + ], + }, + ), + ], +) +def test_merging_tools_metadata(syft_tools_metadata: str, expected_result: Any, tmpdir: Path) -> None: + syft_sbom = { + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "metadata": { + "tools": syft_tools_metadata, + }, + "components": [], + } + + cachi2_sbom = { + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "metadata": { + "tools": [TOOLS_METADATA["cachi2-cyclonedx-1.4"]], + }, + "components": [], + } + + syft_sbom_path = f"{tmpdir}/syft.bom.json" + cachi2_sbom_path = f"{tmpdir}/cachi2.bom.json" + + with open(syft_sbom_path, "w") as file: + json.dump(syft_sbom, file) + + with open(cachi2_sbom_path, "w") as file: + json.dump(cachi2_sbom, file) + + result = merge_sboms(cachi2_sbom_path, syft_sbom_path) + + assert json.loads(result)["metadata"]["tools"] == expected_result + + +def test_invalid_tools_format(tmpdir: Path) -> None: + syft_sbom = { + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "metadata": { + "tools": "invalid", + }, + "components": [], + } + + cachi2_sbom = { + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "metadata": { + "tools": [TOOLS_METADATA["cachi2-cyclonedx-1.4"]], + }, + "components": [], + } + + syft_sbom_path = f"{tmpdir}/syft.bom.json" + cachi2_sbom_path = f"{tmpdir}/cachi2.bom.json" + + with open(syft_sbom_path, "w") as file: + json.dump(syft_sbom, file) + + with open(cachi2_sbom_path, "w") as file: + json.dump(cachi2_sbom, file) + + with pytest.raises(RuntimeError): + merge_sboms(cachi2_sbom_path, syft_sbom_path) diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini new file mode 100644 index 0000000..db6ddad --- /dev/null +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini @@ -0,0 +1,15 @@ +[tox] +env_list = flake8,black,test + +[testenv:test] +deps = -r requirements-test.txt +commands = pytest test_merge_cachi2_sboms.py + +[testenv:flake8] +deps = flake8 +commands = flake8 --max-line-length 120 merge_cachi2_sboms.py test_merge_cachi2_sboms.py + +[testenv:black] +deps = black +commands = black --line-length 120 --check --diff . + diff --git a/sbom-utility-scripts/scripts/merge_syft_sboms.py b/sbom-utility-scripts/scripts/merge_syft_sboms.py new file mode 100644 index 0000000..62c2185 --- /dev/null +++ b/sbom-utility-scripts/scripts/merge_syft_sboms.py @@ -0,0 +1,27 @@ +import json + +# load SBOMs +with open("./sbom-image.json") as f: + image_sbom = json.load(f) + +with open("./sbom-source.json") as f: + source_sbom = json.load(f) + +# fetch unique components from available SBOMs +def get_identifier(component): + return component["name"] + '@' + component.get("version", "") + +image_sbom_components = image_sbom.setdefault("components", []) +existing_components = [get_identifier(component) for component in image_sbom_components] + +source_sbom_components = source_sbom.get("components", []) +for component in source_sbom_components: + if get_identifier(component) not in existing_components: + image_sbom_components.append(component) + existing_components.append(get_identifier(component)) + +image_sbom_components.sort(key=lambda c: get_identifier(c)) + +# write the CycloneDX unified SBOM +with open("./sbom-cyclonedx.json", "w") as f: + json.dump(image_sbom, f, indent=4)