94
94
from pprint import pprint
95
95
from sys import argv
96
96
import json
97
+ import logging
97
98
import os
98
99
import requests
99
100
import shutil
@@ -136,7 +137,6 @@ def locate_docker(self):
136
137
proc = subprocess .Popen (['which' ,'docker' ], stdout = subprocess .PIPE )
137
138
out , err = proc .communicate ()
138
139
lines = out .decode ().split ('\n ' )
139
- print (lines )
140
140
if 'docker' in lines [0 ]:
141
141
return lines [0 ]
142
142
else :
@@ -147,7 +147,7 @@ def pull_container_image(self, image_name):
147
147
args .append (self .docker_path )
148
148
args .append ('pull' )
149
149
args .append (image_name )
150
- return subprocess .run (args )
150
+ return subprocess .run (args , capture_output = True )
151
151
152
152
def get_container_image_history (self , image_name ):
153
153
args = []
@@ -173,7 +173,7 @@ def unravel_container(self):
173
173
args .append (self .imagefile )
174
174
args .append ('-C' )
175
175
args .append (self .imagedir )
176
- return subprocess .run (args )
176
+ return subprocess .run (args , capture_output = True )
177
177
178
178
def read_manifest (self ):
179
179
filename = self .imagedir + "/manifest.json"
@@ -222,7 +222,7 @@ def detect_run(self, options=['--help']):
222
222
cmd .append ('--blackduck.api.token=' + self .token )
223
223
cmd .append ('--blackduck.trust.cert=true' )
224
224
cmd .extend (options )
225
- subprocess .run (cmd )
225
+ return subprocess .run (cmd , capture_output = True )
226
226
227
227
class ContainerImageScanner ():
228
228
@@ -249,7 +249,6 @@ def __init__(
249
249
self .extra_options = []
250
250
if detect_options :
251
251
self .extra_options = detect_options .split (" " )
252
- print ("<--{}-->" .format (self .grouping ))
253
252
self .binary = False
254
253
255
254
def prepare_container_image (self ):
@@ -272,12 +271,11 @@ def prepare_container_image(self):
272
271
history_grouping += str (layer_count ) + ":" + found
273
272
if len (history_grouping ) and self .grouping == '1024:everything' :
274
273
self .grouping = history_grouping
274
+ self .oci_layout = self .docker .read_oci_layout ()
275
275
276
276
def process_container_image_by_user_defined_groups (self ):
277
277
self .manifest = self .docker .read_manifest ()
278
- print (self .manifest )
279
278
self .config = self .docker .read_config ()
280
- print (json .dumps (self .config , indent = 4 ))
281
279
282
280
if self .grouping :
283
281
self .groups = dict (x .split (":" ) for x in self .grouping .split ("," ))
@@ -309,14 +307,12 @@ def process_container_image_by_user_defined_groups(self):
309
307
layer ['shaid' ] = self .config ['rootfs' ]['diff_ids' ][num - 1 ]
310
308
self .layers .append (layer )
311
309
num = num + 1
312
- print (json .dumps (self .layers , indent = 4 ))
310
+ # print (json.dumps(self.layers, indent=4))
313
311
314
312
def process_container_image_by_base_image_info (self ):
315
313
self .manifest = self .docker .read_manifest ()
316
- print (self .manifest )
317
314
self .config = self .docker .read_config ()
318
- print (json .dumps (self .config , indent = 4 ))
319
-
315
+
320
316
self .layers = []
321
317
num = 1
322
318
offset = 0
@@ -342,27 +338,92 @@ def process_container_image_by_base_image_info(self):
342
338
layer ['name' ] = self .project_name + "_" + self .project_version + "_layer_" + str (num )
343
339
self .layers .append (layer )
344
340
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
346
384
347
385
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 ):
348
392
if self .grouping :
349
393
self .process_container_image_by_user_defined_groups ()
350
394
else :
351
395
self .process_container_image_by_base_image_info ()
352
396
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
+
353
403
def submit_layer_scans (self ):
354
404
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
+
366
427
367
428
def adorn_extra_options (self , layer ):
368
429
result = list ()
@@ -396,24 +457,24 @@ def get_base_layers(self):
396
457
if self .base_image :
397
458
imagelist .append (self .base_image )
398
459
399
- print (imagelist )
460
+ # print (imagelist)
400
461
base_layers = []
401
462
for image in imagelist :
402
463
self .docker .initdir ()
403
464
self .docker .pull_container_image (image )
404
465
self .docker .save_container_image (image )
405
466
self .docker .unravel_container ()
406
467
manifest = self .docker .read_manifest ()
407
- print (manifest )
468
+ # print(manifest)
408
469
config = self .docker .read_config ()
409
- print (config )
470
+ # print(config)
410
471
base_layers .extend (config ['rootfs' ]['diff_ids' ])
411
472
return base_layers
412
473
413
474
414
475
def scan_container_image (
415
476
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 ):
417
478
418
479
if hub :
419
480
hub = hub
@@ -431,6 +492,8 @@ def scan_container_image(
431
492
scanner .grouping = '1024:everything'
432
493
else :
433
494
scanner .base_layers = scanner .get_base_layers ()
495
+ if binary :
496
+ scanner .binary = True
434
497
scanner .prepare_container_image ()
435
498
scanner .process_container_image ()
436
499
scanner .submit_layer_scans ()
@@ -450,10 +513,11 @@ def main(argv=None):
450
513
parser .add_argument ('--project-name' ,default = None , type = str , help = "Specify project name (default is container image spec)" )
451
514
parser .add_argument ('--project-version' ,default = None , type = str , help = "Specify project version (default is container image tag/version)" )
452
515
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
+
454
518
args = parser .parse_args ()
455
519
456
- print (args );
520
+ logging . debug (args );
457
521
458
522
if not args .imagespec :
459
523
parser .print_help (sys .stdout )
@@ -474,7 +538,8 @@ def main(argv=None):
474
538
args .dockerfile ,
475
539
args .project_name ,
476
540
args .project_version ,
477
- args .detect_options )
541
+ args .detect_options ,
542
+ args .binary )
478
543
479
544
480
545
if __name__ == "__main__" :
0 commit comments