9494from pprint import pprint
9595from sys import argv
9696import json
97+ import logging
9798import os
9899import requests
99100import shutil
@@ -136,7 +137,6 @@ def locate_docker(self):
136137 proc = subprocess .Popen (['which' ,'docker' ], stdout = subprocess .PIPE )
137138 out , err = proc .communicate ()
138139 lines = out .decode ().split ('\n ' )
139- print (lines )
140140 if 'docker' in lines [0 ]:
141141 return lines [0 ]
142142 else :
@@ -147,7 +147,7 @@ def pull_container_image(self, image_name):
147147 args .append (self .docker_path )
148148 args .append ('pull' )
149149 args .append (image_name )
150- return subprocess .run (args )
150+ return subprocess .run (args , capture_output = True )
151151
152152 def get_container_image_history (self , image_name ):
153153 args = []
@@ -173,7 +173,7 @@ def unravel_container(self):
173173 args .append (self .imagefile )
174174 args .append ('-C' )
175175 args .append (self .imagedir )
176- return subprocess .run (args )
176+ return subprocess .run (args , capture_output = True )
177177
178178 def read_manifest (self ):
179179 filename = self .imagedir + "/manifest.json"
@@ -222,7 +222,7 @@ def detect_run(self, options=['--help']):
222222 cmd .append ('--blackduck.api.token=' + self .token )
223223 cmd .append ('--blackduck.trust.cert=true' )
224224 cmd .extend (options )
225- subprocess .run (cmd )
225+ return subprocess .run (cmd , capture_output = True )
226226
227227class ContainerImageScanner ():
228228
@@ -249,7 +249,6 @@ def __init__(
249249 self .extra_options = []
250250 if detect_options :
251251 self .extra_options = detect_options .split (" " )
252- print ("<--{}-->" .format (self .grouping ))
253252 self .binary = False
254253
255254 def prepare_container_image (self ):
@@ -272,12 +271,11 @@ def prepare_container_image(self):
272271 history_grouping += str (layer_count ) + ":" + found
273272 if len (history_grouping ) and self .grouping == '1024:everything' :
274273 self .grouping = history_grouping
274+ self .oci_layout = self .docker .read_oci_layout ()
275275
276276 def process_container_image_by_user_defined_groups (self ):
277277 self .manifest = self .docker .read_manifest ()
278- print (self .manifest )
279278 self .config = self .docker .read_config ()
280- print (json .dumps (self .config , indent = 4 ))
281279
282280 if self .grouping :
283281 self .groups = dict (x .split (":" ) for x in self .grouping .split ("," ))
@@ -309,14 +307,12 @@ def process_container_image_by_user_defined_groups(self):
309307 layer ['shaid' ] = self .config ['rootfs' ]['diff_ids' ][num - 1 ]
310308 self .layers .append (layer )
311309 num = num + 1
312- print (json .dumps (self .layers , indent = 4 ))
310+ # print (json.dumps(self.layers, indent=4))
313311
314312 def process_container_image_by_base_image_info (self ):
315313 self .manifest = self .docker .read_manifest ()
316- print (self .manifest )
317314 self .config = self .docker .read_config ()
318- print (json .dumps (self .config , indent = 4 ))
319-
315+
320316 self .layers = []
321317 num = 1
322318 offset = 0
@@ -342,27 +338,92 @@ def process_container_image_by_base_image_info(self):
342338 layer ['name' ] = self .project_name + "_" + self .project_version + "_layer_" + str (num )
343339 self .layers .append (layer )
344340 num = num + 1
345- print (json .dumps (self .layers , indent = 4 ))
341+ # print (json.dumps(self.layers, indent=4))
342+
343+ def process_oci_container_image_by_user_defined_groups (self ):
344+ self .manifest = self .docker .read_manifest ()
345+ self .config = self .docker .read_config ()
346+
347+ self .layers = self .config ['history' ]
348+ tagged_layers = [x for x in self .layers if '_group_end' in x .get ('created_by' )]
349+ groups = {re .search ('echo (.+?)_group_end' , str (x ['created_by' ])).group (1 ): self .layers .index (x ) for x in tagged_layers }
350+ logging .debug (f"Container configuration defines following groups { groups } " )
351+ layer_paths = self .manifest [0 ]['Layers' ].copy ()
352+ empty_layers = [x for x in self .layers if x .get ('empty_layer' , False )]
353+ logging .debug (f"Total layers: { len (self .layers )} total paths: { len (layer_paths )} empty layers: { len (empty_layers )} " )
354+
355+ assert len (self .layers ) == len (layer_paths ) + len (empty_layers ), "Something is wrong with this image, Layer math does not add up."
356+
357+ for layer in self .layers :
358+ layer ['index' ] = self .layers .index (layer )
359+ if self .grouping :
360+ layer ['group_name' ] = self .get_group_name (groups , layer ['index' ])
361+ layer ['project_name' ] = "{}_{}" .format (self .project_name ,layer ['group_name' ])
362+ layer ['project_version' ] = self .project_version
363+ layer ['name' ] = "{}_{}_{}_layer_{}" .format (self .project_name ,self .project_version ,layer ['group_name' ],str (layer ['index' ]))
364+ else :
365+ layer ['project_name' ] = self .project_name
366+ layer ['project_version' ] = self .project_version
367+ layer ['name' ] = self .project_name + "_" + self .project_version + "_layer_" + str (layer ['index' ])
368+ if not layer .get ('empty_layer' , False ):
369+ layer ['path' ] = layer_paths .pop (0 )
370+ # print (json.dumps(self.layers, indent=4))
371+
372+ def get_group_name (self , groups , index ):
373+ group_name = 'undefined'
374+ for group , value in groups .items ():
375+ if index <= value :
376+ group_name = group
377+ break
378+ return group_name
379+
380+ def process_oci_container_image_by_base_image_info (self ):
381+ print ("Processing by BAse Image not supported for OCI images" )
382+ sys .exit (1 )
383+ pass
346384
347385 def process_container_image (self ):
386+ if self .oci_layout :
387+ self .process_oci_container_image ()
388+ else :
389+ self .process_docker_container_image ()
390+
391+ def process_docker_container_image (self ):
348392 if self .grouping :
349393 self .process_container_image_by_user_defined_groups ()
350394 else :
351395 self .process_container_image_by_base_image_info ()
352396
397+ def process_oci_container_image (self ):
398+ if self .grouping :
399+ self .process_oci_container_image_by_user_defined_groups ()
400+ else :
401+ self .process_oci_container_image_by_base_image_info ()
402+
353403 def submit_layer_scans (self ):
354404 for layer in self .layers :
355- options = []
356- options .append ('--detect.project.name={}' .format (layer ['project_name' ]))
357- options .append ('--detect.project.version.name="{}"' .format (layer ['project_version' ]))
358- # options.append('--detect.blackduck.signature.scanner.disabled=false')
359- options .append ('--detect.code.location.name={}_{}_code_{}' .format (layer ['name' ],self .image_version ,layer ['path' ]))
360- options .append ('--detect.source.path={}/{}' .format (self .docker .imagedir , layer ['path' ].split ('/' )[0 ]))
361- if self .base_image or self .grouping or self .dockerfile :
362- options .extend (self .adorn_extra_options (layer ))
363- else :
364- options .extend (self .extra_options )
365- self .hub_detect .detect_run (options )
405+ if not layer .get ('empty_layer' , False ):
406+ options = []
407+ options .append ('--detect.project.name={}' .format (layer ['project_name' ]))
408+ options .append ('--detect.project.version.name="{}"' .format (layer ['project_version' ]))
409+ options .append ('--detect.code.location.name={}_{}_code_{}' .format (layer ['name' ],self .image_version ,layer ['path' ]))
410+ if self .binary :
411+ options .append ('--detect.tools=BINARY_SCAN' )
412+ options .append ('--detect.binary.scan.file.path={}/{}' .format (self .docker .imagedir , layer ['path' ]))
413+ else :
414+ options .append ('--detect.tools=SIGNATURE_SCAN' )
415+ if self .oci_layout :
416+ options .append ('--detect.source.path={}/{}' .format (self .docker .imagedir , layer ['path' ]))
417+ else :
418+ options .append ('--detect.source.path={}/{}' .format (self .docker .imagedir , layer ['path' ].split ('/' )[0 ]))
419+ if self .base_image or self .grouping or self .dockerfile :
420+ options .extend (self .adorn_extra_options (layer ))
421+ else :
422+ options .extend (self .extra_options )
423+ logging .info (f"Submitting scan for { layer ['name' ]} " )
424+ completed = self .hub_detect .detect_run (options )
425+ logging .info (f"Detect run for { layer ['name' ]} completed with returncode { completed .returncode } " )
426+
366427
367428 def adorn_extra_options (self , layer ):
368429 result = list ()
@@ -396,24 +457,24 @@ def get_base_layers(self):
396457 if self .base_image :
397458 imagelist .append (self .base_image )
398459
399- print (imagelist )
460+ # print (imagelist)
400461 base_layers = []
401462 for image in imagelist :
402463 self .docker .initdir ()
403464 self .docker .pull_container_image (image )
404465 self .docker .save_container_image (image )
405466 self .docker .unravel_container ()
406467 manifest = self .docker .read_manifest ()
407- print (manifest )
468+ # print(manifest)
408469 config = self .docker .read_config ()
409- print (config )
470+ # print(config)
410471 base_layers .extend (config ['rootfs' ]['diff_ids' ])
411472 return base_layers
412473
413474
414475def scan_container_image (
415476 imagespec , grouping = None , base_image = None , dockerfile = None ,
416- project_name = None , project_version = None , detect_options = None , hub = None ):
477+ project_name = None , project_version = None , detect_options = None , hub = None , binary = False ):
417478
418479 if hub :
419480 hub = hub
@@ -431,6 +492,8 @@ def scan_container_image(
431492 scanner .grouping = '1024:everything'
432493 else :
433494 scanner .base_layers = scanner .get_base_layers ()
495+ if binary :
496+ scanner .binary = True
434497 scanner .prepare_container_image ()
435498 scanner .process_container_image ()
436499 scanner .submit_layer_scans ()
@@ -450,10 +513,11 @@ def main(argv=None):
450513 parser .add_argument ('--project-name' ,default = None , type = str , help = "Specify project name (default is container image spec)" )
451514 parser .add_argument ('--project-version' ,default = None , type = str , help = "Specify project version (default is container image tag/version)" )
452515 parser .add_argument ('--detect-options' ,default = None , type = str , help = "Extra detect options to be passed directly to the detect" )
453-
516+ parser .add_argument ('--binary' , action = 'store_true' , help = "Use Binary Scan instead of signature scan" )
517+
454518 args = parser .parse_args ()
455519
456- print (args );
520+ logging . debug (args );
457521
458522 if not args .imagespec :
459523 parser .print_help (sys .stdout )
@@ -474,7 +538,8 @@ def main(argv=None):
474538 args .dockerfile ,
475539 args .project_name ,
476540 args .project_version ,
477- args .detect_options )
541+ args .detect_options ,
542+ args .binary )
478543
479544
480545if __name__ == "__main__" :
0 commit comments