Skip to content

Commit

Permalink
Support new tool metadata format in the merge syft sbom script
Browse files Browse the repository at this point in the history
CycloneDX 1.5 changes the way to define tools in the metadata section,
and marks the 1.4 way as deprecated.

Syft has adopted the newer format starting from version 0.99.0. This
makes the 'merge_syft_sbom.py' script to fail in case a newer Syft
SBOM is used.

This patch updates the script so both formats can be handled. It assumes
the Cachi2 SBOM is in the newer format.

Signed-off-by: Bruno Pimentel <[email protected]>
  • Loading branch information
brunoapimentel committed Jan 25, 2024
1 parent f651d1f commit 20ddee2
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 32 deletions.
17 changes: 10 additions & 7 deletions tests/unit/data/sboms/cachi2.bom.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,16 @@
}
],
"metadata": {
"tools": [
{
"vendor": "red hat",
"name": "cachi2"
}
]
"tools": {
"components": [
{
"type": "application",
"author": "red hat",
"name": "cachi2"
}
]
}
},
"specVersion": "1.4",
"specVersion": "1.5",
"version": 1
}
31 changes: 18 additions & 13 deletions tests/unit/data/sboms/merged.bom.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:43840228-3fae-4d88-95aa-96512d2510dd",
"specVersion": "1.5",
"serialNumber": "urn:uuid:4370d1ba-7643-4579-8313-bc715da2fa90",
"version": 1,
"metadata": {
"timestamp": "2023-05-03T18:19:41Z",
"tools": [
{
"vendor": "anchore",
"name": "syft",
"version": "0.47.0"
},
{
"vendor": "red hat",
"name": "cachi2"
}
],
"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",
Expand Down
22 changes: 13 additions & 9 deletions tests/unit/data/sboms/syft.bom.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:43840228-3fae-4d88-95aa-96512d2510dd",
"specVersion": "1.5",
"serialNumber": "urn:uuid:4370d1ba-7643-4579-8313-bc715da2fa90",
"version": 1,
"metadata": {
"timestamp": "2023-05-03T18:19:41Z",
"tools": [
{
"vendor": "anchore",
"name": "syft",
"version": "0.47.0"
}
],
"tools": {
"components": [
{
"type": "application",
"author": "anchore",
"name": "syft",
"version": "0.100.0"
}
]
},
"component": {
"bom-ref": "6b8edfe5f2756e0",
"type": "file",
Expand Down
84 changes: 84 additions & 0 deletions tests/unit/test_merge_syft_sbom.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
import json
from pathlib import Path
from typing import Any

import pytest

from utils.merge_syft_sbom 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",
},
}


def test_merge_sboms(data_dir: Path) -> None:
result = merge_sboms(f"{data_dir}/sboms/cachi2.bom.json", f"{data_dir}/sboms/syft.bom.json")
Expand All @@ -11,3 +37,61 @@ def test_merge_sboms(data_dir: Path) -> None:
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.5",
"metadata": {
"tools": {"components": [TOOLS_METADATA["cachi2-cyclonedx-1.5"]]},
},
"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
27 changes: 24 additions & 3 deletions utils/merge_syft_sbom.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ def component_is_duplicated(component: dict[str, Any]) -> bool:
return component_is_duplicated


def _merge_tools_metadata(syft_sbom: dict[Any, Any], cachi2_sbom: dict[Any, Any]) -> None:
"""Add Cachi2 as a tool 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 newer format.
"""
syft_tools = syft_sbom["metadata"]["tools"]
cachi2_components = cachi2_sbom["metadata"]["tools"]["components"]

if type(syft_tools) is dict:
syft_tools["components"].extend(cachi2_components)
elif type(syft_tools) is list:
tools = []

for c in cachi2_components:
tools.append({"name": c["name"], "vendor": c["author"]})

syft_tools.extend(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:
Expand All @@ -134,9 +157,7 @@ def merge_sboms(cachi2_sbom_path: str, syft_sbom_path: str) -> str:

syft_sbom["components"] = filtered_syft_components + cachi2_sbom["components"]

syft_sbom.get("metadata", {}).get("tools", []).extend(
cachi2_sbom.get("metadata", {}).get("tools", [])
)
_merge_tools_metadata(syft_sbom, cachi2_sbom)

return json.dumps(syft_sbom, indent=2)

Expand Down

0 comments on commit 20ddee2

Please sign in to comment.