39
39
Requirements
40
40
41
41
- python3 version 3.8 or newer recommended
42
- - the following packages are used by the script and should be installed
42
+ - The following packages are used by the script and should be installed
43
43
prior to use:
44
44
argparse
45
45
blackduck
60
60
61
61
pip3 install argparse blackduck sys logging time json spdx_tools
62
62
63
- usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE --spdx-file SPDX_FILE --out-file OUT_FILE --project PROJECT_NAME --version VERSION_NAME [--no-verify]
63
+ usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE
64
+ --spdx-file SPDX_FILE --out-file OUT_FILE --project
65
+ PROJECT_NAME --version VERSION_NAME
66
+ [--license LICENSE_NAME] [--no-verify]
67
+ [--no-spdx-validate]
64
68
65
- Parse SPDX file and verify if component names are in current SBOM for given project-version
69
+ Parse SPDX file and verify if component names are in current SBOM for given
70
+ project-version
66
71
67
72
optional arguments:
68
73
-h, --help show this help message and exit
76
81
Project that contains the BOM components
77
82
--version VERSION_NAME
78
83
Version that contains the BOM components
84
+ --license LICENSE_NAME
85
+ License name to use for custom components (default:
86
+ NOASSERTION)
79
87
--no-verify Disable TLS certificate verification
88
+ --no-spdx-validate Disable SPDX validation
80
89
81
90
'''
82
91
@@ -145,6 +154,10 @@ def poll_for_upload(sbom_name):
145
154
sleep_time = 10
146
155
matched_scan = False
147
156
157
+ # TODO also check for api/projects/<ver>/versions/<ver>/codelocations
158
+ # -- status - operationNameCode = ServerScanning, operationName=Scanning, status
159
+ # -- should be COMPLETED, not IN_PROGRESS
160
+ # -- operatinName: Scanning
148
161
# Search for the latest scan matching our SBOM
149
162
# This might be a risk for a race condition
150
163
params = {
@@ -212,10 +225,10 @@ def upload_sbom_file(filename, project, version):
212
225
files = {"file" : (filename , open (filename ,"rb" ), mime_type )}
213
226
fields = {"projectName" : project , "versionName" : version }
214
227
response = bd .session .post ("/api/scan/data" , files = files , data = fields )
215
- logging .info (response )
228
+ logging .debug (response )
216
229
217
230
if response .status_code == 409 :
218
- logging .info (f"File { filename } is already mapped to a different project version" )
231
+ logging .error (f"File { filename } is already mapped to a different project version" )
219
232
220
233
if response .status_code != 201 :
221
234
logging .error (f"Failed to upload SPDX file:" )
@@ -227,41 +240,23 @@ def upload_sbom_file(filename, project, version):
227
240
228
241
229
242
# Lookup the given pURL in the BD KB.
230
- # If successfully matched, update the associated package name and version with the data from the KB.
231
- # This will improve the accuracy of later lookups. We are replacing the SPDX input data with the
232
- # data stored in the KB.
233
243
#
234
244
# Inputs:
235
- # matchname - Name of package from the SPDX input file
236
- # matchver - Version of package
237
- # extref - pURL to look up
245
+ # extref - pURL to look up
238
246
#
239
247
# Returns:
240
- # purlmatch - boolean (True if successful KB lookup)
241
- # matchname - Original parameter OR updated to reflect KB lookup name
242
- # matchver - Original parameter OR updated to reflect KB lookup version
243
- def find_comp_in_kb (matchname , matchver , extref ):
244
- purlmatch = False
248
+ # If match: API matching data (the "result" object)
249
+ # No match: None
250
+ def find_comp_in_kb (extref ):
245
251
params = {
246
252
'packageUrl' : extref
247
253
}
248
254
for result in bd .get_items ("/api/search/purl-components" , params = params ):
249
- # This query should result in exactly 1 match
250
- purlmatch = True
251
- # Override the spdx name and use the known KB name
252
- if matchname != result ['componentName' ]:
253
- print (f"Renaming { matchname } -> { result ['componentName' ]} " )
254
- matchname = result ['componentName' ]
255
- # Override the spdx version and use the string from KB
256
- # for example, v2.8.5 -> 2.8.5
257
- if matchver != result ['versionName' ]:
258
- print (f"Renaming { matchver } -> { result ['versionName' ]} " )
259
- matchver = result ['versionName' ]
255
+ # Should be exactly 1 match when successful
256
+ return (result )
260
257
261
- return (purlmatch , matchname , matchver )
262
-
263
- # fall through -- lookup failed, so we keep the original name/ver
264
- return (purlmatch , matchname , matchver )
258
+ # Fall through -- lookup failed
259
+ return (None )
265
260
266
261
267
262
# Locate component name + version in BOM
@@ -472,8 +467,13 @@ def add_to_sbom(proj_version_url, comp_ver_url):
472
467
# This will exit if it fails
473
468
poll_for_upload (document .creation_info .name )
474
469
475
- # some little debug/test stubs
470
+ # some debug/test stubs
476
471
# TODO: delete these
472
+ #ver="https://purl-validation.saas-staging.blackduck.com/api/projects/c2b4463f-7996-4c45-8443-b69b4f82ef1d/versions/67e4f6f5-2f42-42c4-9b69-e39bad55f907"
473
+ #comp = "https://purl-validation.saas-staging.blackduck.com/api/components/fc0a76fe-70a4-4afa-9a94-c3c22d63454f/versions/fabaabb9-3b9a-4b5f-850a-39fe84c4cfc4"
474
+ #add_to_sbom(ver, comp)
475
+ #quit()
476
+
477
477
#matchcomp, matchver = find_cust_comp("ipaddress", "1.0.23")
478
478
#if matchcomp:
479
479
# print("matched comp")
@@ -493,12 +493,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
493
493
#add_to_sbom(pv, cv)
494
494
#quit()
495
495
496
- # Open unmatched component file
497
- # Will save name, spdxid, version, and origin/purl for later in json format:
498
- # "name": "react-bootstrap",
499
- # "spdx_id": "SPDXRef-Pkg-react-bootstrap-2.1.2-30223",
500
- # "version": "2.1.2",
501
- # "origin": null
496
+ # Open unmatched component file to save name, spdxid, version, and
497
+ # origin/purl for later in json format
502
498
# TODO this try/except isn't quite right
503
499
try : outfile = open (args .out_file , 'w' )
504
500
except :
@@ -531,16 +527,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
531
527
532
528
logging .debug (f"Found { project ['name' ]} :{ version ['versionName' ]} " )
533
529
534
- # situations to consider + actions
535
- # 1) No purl available : check SBOM for comp+ver, then add cust comp + add to SBOM
536
- # 2) Have purl + found in KB
537
- # - In SBOM? -> done
538
- # - Else -> add known KB comp to SBOM
539
- # *** this shouldn't happen in theory
540
- # 3) Have purl + not in KB (main case we are concerned with)
541
- # - In SBOM? (maybe already added or whatever?) -> done
542
- # - Else -> add cust comp + add to SBOM (same as 1)
543
-
544
530
# Stats to track
545
531
bom_matches = 0
546
532
kb_matches = 0
@@ -560,60 +546,81 @@ def add_to_sbom(proj_version_url, comp_ver_url):
560
546
purlmatch = False
561
547
matchname = package .name
562
548
matchver = package .version
549
+ print (f"Processing SPDX package: { matchname } { matchver } ...." )
563
550
# Tracking unique package name + version from spdx file
564
551
packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
565
552
566
- # NOTE: BD can change the original component name
567
- # EX: "React" -> "React from Facebook"
568
553
if package .external_references :
569
- inkb , matchname , matchver = find_comp_in_kb (matchname , matchver , package .external_references [0 ].locator )
570
- if inkb : kb_matches += 1
554
+ # TODO need to handle the possiblity of:
555
+ # A) multiple extrefs
556
+ # B) an extref that is not a purl
557
+ # -- referenceType should be "purl" - ignore others?
558
+ kb_match = find_comp_in_kb (package .external_references [0 ].locator )
559
+ if (kb_match ):
560
+ # Update package name and version to reflect the KB name/ver
561
+ print (f" KB match for { package .name } { package .version } " )
562
+ kb_matches += 1
563
+ matchname = kb_match ['componentName' ]
564
+ matchver = kb_match ['versionName' ]
565
+ else :
566
+ print (f" No KB match for { package .name } { package .version } " )
571
567
else :
572
568
nopurl += 1
573
- print ("No pURL found for component: " )
574
- print (" " + package .name )
575
- print (" " + package .spdx_id )
576
- print (" " + package .version )
569
+ kb_match = None
570
+ print (f"No pURL provided for { package .name } { package .version } " )
577
571
578
572
if find_comp_in_bom (matchname , matchver , version ):
579
573
bom_matches += 1
580
- print (" Found comp match in BOM: " + matchname + matchver )
574
+ print (f" Found component in BOM: { matchname } { matchver } " )
575
+ # It's in the BOM so we are happy
576
+ # Everything else below is related to adding to the BOM
577
+ continue
578
+
579
+ # If we've gotten this far, the package is not in the BOM.
580
+ # Now we need to figure out:
581
+ # - Is it already in the KB and we need to add it? (should be rare)
582
+ # - Do we need to add a custom component?
583
+ # - Do we need to add a version to an existing custom component?
584
+ nomatch += 1
585
+ print (f" Not present in BOM: { matchname } { matchver } " )
586
+ comp_data = {
587
+ "name" : package .name ,
588
+ "spdx_id" : package .spdx_id ,
589
+ "version" : package .version ,
590
+ "origin" : extref
591
+ }
592
+ comps_out .append (comp_data )
593
+
594
+ # KB match was successful, but it wasn't in the BOM for some reason
595
+ if kb_match :
596
+ print (f" WARNING: { matchname } { matchver } in KB but not in SBOM" )
597
+ add_to_sbom (proj_version_url , kb_match ['version' ])
598
+ # temp debug to find this case
599
+ quit ()
600
+ # short-circuit the rest
601
+ continue
602
+
603
+ # Check if custom component already exists
604
+ comp_url , comp_ver_url = find_cust_comp (package .name , package .version )
605
+
606
+ if not comp_url :
607
+ # Custom component did not exist, so create it
608
+ cust_comp_count += 1
609
+ comp_ver_url = create_cust_comp (package .name , package .version ,
610
+ args .license_name )
611
+ elif comp_url and not comp_ver_url :
612
+ # Custom component existed, but not the version we care about
613
+ cust_ver_count += 1
614
+ print (f" Adding version { package .version } to custom component { package .name } " )
615
+ comp_ver_url = create_cust_comp_ver (comp_url , package .version , args .license_name )
581
616
else :
582
- nomatch += 1
583
- comp_data = {
584
- "name" : package .name ,
585
- "spdx_id" : package .spdx_id ,
586
- "version" : package .version ,
587
- "origin" : extref
588
- }
589
- comps_out .append (comp_data )
590
-
591
- # TODO what about: KB exists but not in BOM??
592
- # find_cust_comp is not generic enough for that situation
593
- #if inkb:
594
- # TODO handle add KB match to BOM here, short-circuit steps below
617
+ print (" Custom component already exists, not in SBOM" )
595
618
596
- # Check if custom component already exists
597
- comp_url , comp_ver_url = find_cust_comp (package .name , package .version )
598
-
599
- if not comp_url :
600
- # Custom component did not exist, so create it
601
- cust_comp_count += 1
602
- comp_ver_url = create_cust_comp (package .name , package .version ,
603
- args .license_name )
604
- elif comp_url and not comp_ver_url :
605
- # Custom component existed, but not the version we care about
606
- cust_ver_count += 1
607
- print (f"Adding version { package .version } to custom component { package .name } " )
608
- comp_ver_url = create_cust_comp_ver (comp_url , package .version , args .license_name )
609
- else :
610
- print ("Custom component already exists, not in SBOM" )
619
+ # Shouldn't be possible
620
+ assert (comp_ver_url ), f"No component URL found for { package .name } { package .version } "
611
621
612
- # is this possible?
613
- assert (comp_ver_url ), f"No comp_ver URL found for { package .name } { package .version } "
614
-
615
- print (f"Adding component to SBOM: { package .name } aka { matchname } { package .version } " )
616
- add_to_sbom (proj_version_url , comp_ver_url )
622
+ print (f" Adding component to SBOM: { package .name } aka { matchname } { package .version } " )
623
+ add_to_sbom (proj_version_url , comp_ver_url )
617
624
618
625
# Save unmatched components
619
626
json .dump (comps_out , outfile )
@@ -622,9 +629,9 @@ def add_to_sbom(proj_version_url, comp_ver_url):
622
629
print ("\n Stats: " )
623
630
print ("------" )
624
631
print (f" SPDX packages processed: { package_count } " )
625
- print (f" Non matches: { nomatch } " )
626
- print (f" KB matches: { kb_matches } " )
632
+ print (f" Packages missing from BOM: { nomatch } " )
627
633
print (f" BOM matches: { bom_matches } " )
634
+ print (f" KB matches: { kb_matches } " )
628
635
print (f" Packages missing purl: { nopurl } " )
629
636
print (f" Custom components created: { cust_comp_count } " )
630
637
print (f" Custom component versions created: { cust_ver_count } " )
0 commit comments