1010import urllib .parse
1111import urllib .request
1212
13+ from typing import Optional
14+
15+
1316excluded_products = [
1417 "hello-world" ,
1518 "java-base" ,
@@ -264,7 +267,7 @@ def _build_base_env(secobserve_api_token: str, product_name: str, branch_name: s
264267_GRYPE_REPORT = "grype.json"
265268
266269
267- def _combined_scan_script (env : dict , mode : str ) -> str :
270+ def _combined_scan_script (env : dict , mode : str , upload_sbom : Optional [ bool ] = False ) -> str :
268271 """Return a shell script that scans with Trivy and Grype, then uploads results.
269272
270273 Trivy and Grype scan the same target independently, so they are launched
@@ -279,29 +282,33 @@ def _combined_scan_script(env: dict, mode: str) -> str:
279282
280283 The existing per-scanner entrypoints are reused (with ``SO_UPLOAD=false`` so
281284 they only scan) to avoid duplicating the scanner invocation flags here.
282-
283- In ``sbom`` mode the target itself is a CycloneDX SBOM, so it is additionally
284- uploaded to SecObserve. In ``image`` mode there is no SBOM file to upload.
285285 """
286- script = (
287- "export TRIVY_NO_PROGRESS=true\n "
288- f"SO_UPLOAD=false REPORT_NAME={ _TRIVY_REPORT } /entrypoints/entrypoint_trivy_{ mode } .sh &\n "
289- "trivy_pid=$!\n "
290- f"SO_UPLOAD=false REPORT_NAME={ _GRYPE_REPORT } /entrypoints/entrypoint_grype_{ mode } .sh &\n "
291- "grype_pid=$!\n "
292- 'wait "$trivy_pid" || echo "WARNING: Trivy scan failed"\n '
293- 'wait "$grype_pid" || echo "WARNING: Grype scan failed"\n '
294- f"if [ -f { _TRIVY_REPORT } ]; then SO_FILE_NAME={ _TRIVY_REPORT } SO_PARSER_NAME=CycloneDX "
295- 'file_upload_observations.sh; else echo "WARNING: no Trivy report to upload"; fi\n '
296- f"if [ -f { _GRYPE_REPORT } ]; then SO_FILE_NAME={ _GRYPE_REPORT } SO_PARSER_NAME=CycloneDX "
297- 'file_upload_observations.sh; else echo "WARNING: no Grype report to upload"; fi\n '
298- )
299- if mode == "sbom" :
300- script += f"SO_FILE_NAME={ env .get ('TARGET' )} file_upload_sbom.sh\n "
286+
287+ if upload_sbom :
288+ if mode == "sbom" :
289+ script = f"SO_FILE_NAME={ env .get ('TARGET' )} file_upload_sbom.sh\n "
290+ else :
291+ print (f"Can't upload SBOM because mode is { mode } " )
292+ script = ""
293+ else :
294+ script = (
295+ "export TRIVY_NO_PROGRESS=true\n "
296+ f"SO_UPLOAD=false REPORT_NAME={ _TRIVY_REPORT } /entrypoints/entrypoint_trivy_{ mode } .sh &\n "
297+ "trivy_pid=$!\n "
298+ f"SO_UPLOAD=false REPORT_NAME={ _GRYPE_REPORT } /entrypoints/entrypoint_grype_{ mode } .sh &\n "
299+ "grype_pid=$!\n "
300+ 'wait "$trivy_pid" || echo "WARNING: Trivy scan failed"\n '
301+ 'wait "$grype_pid" || echo "WARNING: Grype scan failed"\n '
302+ f"if [ -f { _TRIVY_REPORT } ]; then SO_FILE_NAME={ _TRIVY_REPORT } SO_PARSER_NAME=CycloneDX "
303+ 'file_upload_observations.sh; else echo "WARNING: no Trivy report to upload"; fi\n '
304+ f"if [ -f { _GRYPE_REPORT } ]; then SO_FILE_NAME={ _GRYPE_REPORT } SO_PARSER_NAME=CycloneDX "
305+ 'file_upload_observations.sh; else echo "WARNING: no Grype report to upload"; fi\n '
306+ )
307+
301308 return script
302309
303310
304- def _run_combined_scan (env : dict , mode : str ) -> None :
311+ def _run_combined_scan (env : dict , mode : str , upload_sbom : Optional [ bool ] = False ) -> None :
305312 """Run Trivy and Grype in a single container for one target, then upload."""
306313 cmd = [
307314 "docker" , "run" ,
@@ -312,7 +319,7 @@ def _run_combined_scan(env: dict, mode: str) -> None:
312319 for key , value in env .items ():
313320 cmd .extend (["-e" , f"{ key } ={ value } " ])
314321 cmd .append (SECOBSERVE_SCANNER_IMAGE )
315- cmd .extend (["-c" , _combined_scan_script (env , mode )])
322+ cmd .extend (["-c" , _combined_scan_script (env , mode , upload_sbom )])
316323
317324 print (f"docker run (combined trivy+grype, { mode } mode) TARGET={ env .get ('TARGET' )} " )
318325 subprocess .run (cmd )
@@ -429,13 +436,17 @@ def main():
429436 for release in releases :
430437 scan_release (secobserve_api_token , release )
431438 sys .exit (0 )
439+ elif sys .argv [1 ] == "upload-sbom-release" :
440+ secobserve_api_token = sys .argv [2 ]
441+ release = sys .argv [3 ]
442+ scan_release (secobserve_api_token , release , upload_sbom = True )
432443 else :
433444 secobserve_api_token = sys .argv [2 ]
434445 release = sys .argv [3 ]
435446 scan_release (secobserve_api_token , release )
436447
437448
438- def scan_release (secobserve_api_token : str , release : str ) -> None :
449+ def scan_release (secobserve_api_token : str , release : str , upload_sbom : Optional [ bool ] = False ) -> None :
439450 """Scan all operator and product images of a single SDP release."""
440451 checkout = "main" if release == DEV_RELEASE else "tags/" + release
441452
@@ -500,6 +511,7 @@ def scan_release(secobserve_api_token: str, release: str) -> None:
500511 f"{ REGISTRY_URL } /sdp/{ product_name } :{ release } -{ arch } " ,
501512 product_name ,
502513 f"{ release } -{ arch } " ,
514+ upload_sbom ,
503515 )
504516
505517 if use_conf_py :
@@ -515,6 +527,7 @@ def scan_release(secobserve_api_token: str, release: str) -> None:
515527 f"{ REGISTRY_URL } /sdp/{ product_name } :{ product_version } -{ arch } " ,
516528 product_name ,
517529 f"{ product_version } -{ arch } " ,
530+ upload_sbom ,
518531 )
519532 else :
520533 for product_name , versions in product_versions .items ():
@@ -527,6 +540,7 @@ def scan_release(secobserve_api_token: str, release: str) -> None:
527540 f"{ REGISTRY_URL } /sdp/{ product_name } :{ product_version } -{ arch } " ,
528541 product_name ,
529542 f"{ product_version } -{ arch } " ,
543+ upload_sbom ,
530544 )
531545
532546 # Scan additional infrastructure/third-party images using Harbor API tag discovery.
@@ -546,6 +560,7 @@ def scan_image(
546560 image : str ,
547561 product_name : str ,
548562 branch_name : str ,
563+ upload_sbom : Optional [bool ] = False ,
549564) -> None :
550565 mode = "sbom"
551566 extract_sbom_cmd = [
@@ -582,7 +597,7 @@ def scan_image(
582597
583598 env = _build_base_env (secobserve_api_token , product_name , branch_name )
584599 env ["TARGET" ] = image if mode == "image" else "/tmp/bom.json"
585- _run_combined_scan (env , mode )
600+ _run_combined_scan (env , mode , upload_sbom )
586601
587602
588603if __name__ == "__main__" :
0 commit comments