@@ -97,18 +97,12 @@ def detect_sbom_format(sbom):
97
97
else :
98
98
raise ValueError ("Unknown SBOM format" )
99
99
100
+
100
101
def parse_args ():
101
102
parser = argparse .ArgumentParser (
102
103
description = "Updates the sbom file with base images data based on the provided files"
103
104
)
104
105
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
- )
112
106
parser .add_argument (
113
107
"--base-images-from-dockerfile" ,
114
108
type = pathlib .Path ,
@@ -129,28 +123,93 @@ def parse_args():
129
123
return args
130
124
131
125
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
141
128
142
- relations_map = {}
143
- relations_inverse_map = {}
129
+ :param relationships: (List) - List of relationships in the SBOM
144
130
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
+ """
148
135
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" ]
152
140
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
154
213
155
214
156
215
def main ():
@@ -174,86 +233,7 @@ def main():
174
233
else :
175
234
sbom .update ({"formulation" : [{"components" : base_images_sbom_components }]})
176
235
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 )
257
237
# merge newly created packages for build tools with existing packages
258
238
sbom ["packages" ] = sbom .get ("packages" , []) + packages
259
239
# merge newly created relationships of the build tools with existing relationships
0 commit comments