Skip to content

Commit 7676a7b

Browse files
committed
- Simplified root package discovery
- spdx_find_doc_and_root_package wraps spdx related code - fixed isodate generator - fixed annotator string - build deps are generated as SPDXRef-Image- instead of SPDXRef-container- - added test_main_input_sbom_spdx_with_packages Signed-off-by: Jindrich Luza <[email protected]>
1 parent 0303928 commit 7676a7b

File tree

9 files changed

+1084
-177
lines changed

9 files changed

+1084
-177
lines changed

sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py

+85-105
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,12 @@ def detect_sbom_format(sbom):
9797
else:
9898
raise ValueError("Unknown SBOM format")
9999

100+
100101
def parse_args():
101102
parser = argparse.ArgumentParser(
102103
description="Updates the sbom file with base images data based on the provided files"
103104
)
104105
parser.add_argument("--sbom", type=pathlib.Path, help="Path to the sbom file", required=True)
105-
parser.add_argument(
106-
"--sbom-type",
107-
choices=["spdx", "cyclonedx"],
108-
default="cyclonedx",
109-
help="Type of the sbom file",
110-
required=True,
111-
)
112106
parser.add_argument(
113107
"--base-images-from-dockerfile",
114108
type=pathlib.Path,
@@ -129,28 +123,93 @@ def parse_args():
129123
return args
130124

131125

132-
def map_relationships(relationships):
133-
"""Map relationships of spdx element.
134-
Method returns triplet containing root element, map of relations and inverse map of relations.
135-
Root element is considered as element which is not listed as related document
136-
in any of the relationships. Relationship map is dict of {key: value} where key is spdx
137-
element and list of related elements is the value.
138-
Inverse map is dict of {key: value} where key is related spdx element in the relation ship
139-
and value is spdx element.
140-
"""
126+
def spdx_find_doc_and_root_package(relationships):
127+
"""Find SPDX root package and document in the SBOM
141128
142-
relations_map = {}
143-
relations_inverse_map = {}
129+
:param relationships: (List) - List of relationships in the SBOM
144130
145-
for relation in relationships:
146-
relations_map.setdefault(relation["spdxElementId"], []).append(relation["relatedSpdxElement"])
147-
relations_inverse_map[relation["relatedSpdxElement"]] = relation["spdxElementId"]
131+
Method scans relationships for relationshipType "DESCRIBES" and returns
132+
relatedSpdxElement and spdxElementId which are SPDX root package and document.
133+
In the case there's no relationship with relationshipType "DESCRIBES" ValueError is raised.
134+
"""
148135

149-
parent_element = None
150-
for parent_element in relations_map.keys():
151-
if parent_element not in relations_inverse_map:
136+
for relationship in relationships:
137+
if relationship["relationshipType"] == "DESCRIBES":
138+
root_package1 = relationship["relatedSpdxElement"]
139+
doc = relationship["spdxElementId"]
152140
break
153-
return parent_element, relations_map, relations_inverse_map
141+
else:
142+
raise ValueError("No DESCRIBES relationship found in the SBOM")
143+
return root_package1, doc
144+
145+
146+
def spdx_create_dependency_package(component, annotation_date):
147+
"""Create SPDX package for the base image component."""
148+
149+
# Calculate unique identifier SPDXID based on the component name and purl
150+
# See: https://github.com/konflux-ci/architecture/blob/main/ADR/0044-spdx-support.md
151+
SPDXID = f"SPDXRef-Image-{component['name']}-" + f"{hashlib.sha256(component['purl'].encode()).hexdigest()}"
152+
package = {
153+
"SPDXID": SPDXID,
154+
"name": component["name"],
155+
"downloadLocation": "NOASSERTION",
156+
# See more info about external refs here:
157+
# https://spdx.github.io/spdx-spec/v2.3/package-information/#7211-description
158+
"externalRefs": [
159+
{
160+
"referenceCategory": "PACKAGE-MANAGER",
161+
"referenceType": "purl",
162+
"referenceLocator": component["purl"],
163+
}
164+
],
165+
# Annotations are used to provide cyclonedx custom properties
166+
# as json string
167+
# See: https://github.com/konflux-ci/architecture/blob/main/ADR/0044-spdx-support.md
168+
"annotations": [
169+
{
170+
"annotator": "Tool: konflux:jsonencoded",
171+
"annotationDate": annotation_date,
172+
"annotationType": "OTHER",
173+
"comment": json.dumps(
174+
{"name": property["name"], "value": property["value"]},
175+
separators=(",", ":"),
176+
),
177+
}
178+
for property in component["properties"]
179+
],
180+
}
181+
return package, SPDXID
182+
183+
184+
def create_build_relationship(SPDXID, root_package1):
185+
return {
186+
"spdxElementId": SPDXID,
187+
"relatedSpdxElement": root_package1,
188+
"relationshipType": "BUILD_TOOL_OF",
189+
}
190+
191+
192+
def create_build_packages_and_relationships(sbom, base_images_sbom_components):
193+
"""Create SPDX packages and relationships for base images components.
194+
195+
:param sbom: (Dict) - SBOM data
196+
:param base_images_sbom_components: (List) - List of base images components
197+
198+
Method creates SPDX packages for base images components and relationships
199+
"""
200+
201+
packages = []
202+
relationships = []
203+
root_package, doc = spdx_find_doc_and_root_package(sbom["relationships"])
204+
annotation_date = datetime.datetime.now().isoformat()[:-7] + "Z"
205+
for component in base_images_sbom_components:
206+
# create dependency package for each base image
207+
package, SPDXID = spdx_create_dependency_package(component, annotation_date)
208+
209+
packages.append(package)
210+
# Add relationship for parsed base image components and root package
211+
relationships.append(create_build_relationship(SPDXID, root_package))
212+
return packages, relationships
154213

155214

156215
def main():
@@ -174,86 +233,7 @@ def main():
174233
else:
175234
sbom.update({"formulation": [{"components": base_images_sbom_components}]})
176235
else:
177-
root_element1, map1, inverse_map1 = map_relationships(sbom["relationships"])
178-
179-
packages = []
180-
relationships = []
181-
182-
# Try to calculate root package represeting the container image or directory, which was
183-
# used to build the SBOM, based on the relationships maps.
184-
# SPDX has relationsship ROOT-ID DESCRIBES MIDDLE-ID which express the fact the SBOM documents
185-
# describes container image or directory represented by MIDDLE-ID package.
186-
root_package1 = None
187-
for r, contains in map1.items():
188-
# root package is the one which contains another elements and is in relationship with
189-
# the document element where it stand as relatedSpdxElement
190-
if contains and inverse_map1.get(r) == root_element1:
191-
root_package1 = r
192-
# If not root package is found then create one with ID "Uknown" as source for the SBOM
193-
# is not known.
194-
if not root_package1:
195-
root_package1 = "SPDXRef-DocumentRoot-Unknown-"
196-
packages.append(
197-
{
198-
"SPDXID": "SPDXRef-DocumentRoot-Unknown-",
199-
"name": "",
200-
"downloadLocation": "NOASSERTION",
201-
}
202-
)
203-
relationships.append(
204-
{
205-
"spdxElementId": root_element1 or sbom["SPDXID"],
206-
"relatedSpdxElement": "SPDXRef-DocumentRoot-Unknown-",
207-
"relationshipType": "DESCRIBES",
208-
}
209-
)
210-
211-
annotation_date = datetime.datetime.now().isoformat()
212-
for component in base_images_sbom_components:
213-
# Calculate unique identifier SPDXID based on the component name and purl
214-
SPDXID = (
215-
f"SPDXRef-{component['type']}-{component['name']}-"
216-
+ f"{hashlib.sha256(component['purl'].encode()).hexdigest()}"
217-
)
218-
packages.append(
219-
{
220-
"SPDXID": SPDXID,
221-
"name": component["name"],
222-
"downloadLocation": "NOASSERTION",
223-
# See more info about external refs here:
224-
# https://spdx.github.io/spdx-spec/v2.3/package-information/#7211-description
225-
"externalRefs": [
226-
{
227-
"referenceCategory": "PACKAGE-MANAGER",
228-
"referenceType": "purl",
229-
"referenceLocator": component["purl"],
230-
}
231-
],
232-
# Annotations are used to provide cyclonedx custom properties
233-
# as json string
234-
"annotations": [
235-
{
236-
"annotator": "Tool:konflux:jsonencoded",
237-
"annotationDate": annotation_date,
238-
"annotationType": "OTHER",
239-
"comment": json.dumps(
240-
{"name": property["name"], "value": property["value"]},
241-
separators=(",", ":"),
242-
),
243-
}
244-
for property in component["properties"]
245-
],
246-
}
247-
)
248-
# Add relationship for parsed base image components and "middle" element which wraps
249-
# all spdx packages, but it's not spdx document itself.
250-
relationships.append(
251-
{
252-
"spdxElementId": SPDXID,
253-
"relatedSpdxElement": root_package1,
254-
"relationshipType": "BUILD_TOOL_OF",
255-
}
256-
)
236+
packages, relationships = create_build_packages_and_relationships(sbom, base_images_sbom_components)
257237
# merge newly created packages for build tools with existing packages
258238
sbom["packages"] = sbom.get("packages", []) + packages
259239
# merge newly created relationships of the build tools with existing relationships

0 commit comments

Comments
 (0)