diff --git a/tern/__main__.py b/tern/__main__.py index 8f93e1ab..755bd373 100755 --- a/tern/__main__.py +++ b/tern/__main__.py @@ -70,8 +70,8 @@ def check_image_input(options): logger.critical(errors.incorrect_raw_option) sys.exit(1) # Check if the image string has the right format - if options.docker_image: - if not check_image_string(options.docker_image): + if options.image: + if not check_image_string(options.image): logger.critical(errors.incorrect_image_string_format) sys.exit(1) @@ -108,9 +108,9 @@ def do_main(args): drun.execute_dockerfile(args) else: logger.critical("Currently --layer/-y can only be used with" - " --docker-image/-i") + " --image/-i") sys.exit(1) - elif args.docker_image or args.raw_image: + elif args.image or args.raw_image: check_image_input(args) # If the checks are OK, execute for docker image crun.execute_image(args) @@ -167,15 +167,13 @@ def main(): parser_report.add_argument('-d', '--dockerfile', type=check_file_existence, help="Dockerfile used to build the Docker" " image") - parser_report.add_argument('-i', '--docker-image', - help="Docker image that exists locally -" - " image:tag" - " The option can be used to pull docker" - " images by digest as well -" - " @:") + parser_report.add_argument('-i', '--image', + help="A container image referred either by " + " repo:tag or repo@digest-type:digest") parser_report.add_argument('-w', '--raw-image', metavar='FILE', help="Raw container image that exists locally " - "in the form of a tar archive.") + "in the form of a tar archive. Only the output" + "of 'docker save' is supported") parser_report.add_argument('-y', '--layer', metavar='LAYER_NUMBER', const=1, action='store', dest='load_until_layer', diff --git a/tern/analyze/default/container/image.py b/tern/analyze/default/container/image.py index 098aacad..65b0e4a4 100644 --- a/tern/analyze/default/container/image.py +++ b/tern/analyze/default/container/image.py @@ -13,6 +13,7 @@ from tern.classes.notice import Notice from tern.classes.docker_image import DockerImage +from tern.classes.oci_image import OCIImage from tern.utils import constants from tern.analyze import passthrough from tern.analyze.default.container import single_layer @@ -23,14 +24,19 @@ logger = logging.getLogger(constants.logger_name) -def load_full_image(image_tag_string, load_until_layer=0): - '''Create image object from image name and tag and return the object. - Loads only as many layers as needed.''' - test_image = DockerImage(image_tag_string) +def load_full_image(image_tag_string, image_type='oci', load_until_layer=0): + """Create image object from image name and tag and return the object. + The kind of image object is created based on the image_type. + image_type = oci OR docker + Loads only as many layers as needed.""" + if image_type == 'oci': + image = OCIImage(image_tag_string) + elif image_type == 'docker': + image = DockerImage(image_tag_string) failure_origin = formats.image_load_failure.format( - testimage=test_image.repotag) + testimage=image.repotag) try: - test_image.load_image(load_until_layer) + image.load_image(load_until_layer) except (NameError, subprocess.CalledProcessError, IOError, @@ -38,9 +44,9 @@ def load_full_image(image_tag_string, load_until_layer=0): ValueError, EOFError) as error: logger.warning('Error in loading image: %s', str(error)) - test_image.origins.add_notice_to_origins( + image.origins.add_notice_to_origins( failure_origin, Notice(str(error), 'error')) - return test_image + return image def default_analyze(image_obj, options): diff --git a/tern/analyze/default/container/run.py b/tern/analyze/default/container/run.py index 0346881c..ec80a62c 100644 --- a/tern/analyze/default/container/run.py +++ b/tern/analyze/default/container/run.py @@ -14,7 +14,7 @@ from tern.report import report from tern.report import formats from tern import prep -from tern.load import docker_api +from tern.load import skopeo from tern.analyze import common from tern.analyze.default.container import image as cimage @@ -27,28 +27,20 @@ def extract_image(args): """The image can either be downloaded from a container registry or provided as an image tarball. Extract the image into a working directory accordingly Return an image name and tag and an image digest if it exists""" - if args.docker_image: - # extract the docker image - image_attrs = docker_api.dump_docker_image(args.docker_image) - if image_attrs: - # repo name and digest is preferred, but if that doesn't exist - # the repo name and tag will do. If neither exist use repo Id. - if image_attrs['Id']: - image_string = image_attrs['Id'] - if image_attrs['RepoTags']: - image_string = image_attrs['RepoTags'][0] - if image_attrs['RepoDigests']: - image_string = image_attrs['RepoDigests'][0] - return image_string - logger.critical("Cannot extract Docker image") + if args.image: + # download the image + result = skopeo.pull_image(args.image) + if result: + return 'oci', args.image + logger.critical("Cannot download Container image: \"%s\"", args.image) if args.raw_image: # for now we assume that the raw image tarball is always # the product of "docker save", hence it will be in # the docker style layout if rootfs.extract_tarfile(args.raw_image, rootfs.get_working_dir()): - return args.raw_image - logger.critical("Cannot extract raw image") - return None + return 'docker', args.raw_image + logger.critical("Cannot extract raw Docker image") + return None, None def setup(image_obj): @@ -72,11 +64,11 @@ def teardown(image_obj): def execute_image(args): """Execution path for container images""" logger.debug('Starting analysis...') - image_string = extract_image(args) + image_type, image_string = extract_image(args) # If the image has been extracted, load the metadata - if image_string: + if image_type and image_string: full_image = cimage.load_full_image( - image_string, args.load_until_layer) + image_string, image_type, args.load_until_layer) # check if the image was loaded successfully if full_image.origins.is_empty(): # Add an image origin here diff --git a/tern/analyze/default/debug/run.py b/tern/analyze/default/debug/run.py index 081653c2..45e7ff63 100644 --- a/tern/analyze/default/debug/run.py +++ b/tern/analyze/default/debug/run.py @@ -26,10 +26,10 @@ from tern.analyze.default.container import multi_layer -def check_image_obj(image_string): +def check_image_obj(image_string, image_type): """Return the image object and if it was loaded successfully""" if image_string: - full_image = cimage.load_full_image(image_string) + full_image = cimage.load_full_image(image_string, image_type) if full_image.origins.is_empty(): return full_image, True print("Something went wrong in loading the image") @@ -190,9 +190,9 @@ def recover(): def execute_debug(args): """Debug container images""" - if args.docker_image: - image_string = run.extract_image(args) - full_image, success = check_image_obj(image_string) + if args.image: + image_type, image_string = run.extract_image(args) + full_image, success = check_image_obj(image_string, image_type) if success: if args.keys: # we have an image to mount and some scripts to invoke diff --git a/tern/analyze/default/dockerfile/run.py b/tern/analyze/default/dockerfile/run.py index 8ea865fd..42bd1139 100644 --- a/tern/analyze/default/dockerfile/run.py +++ b/tern/analyze/default/dockerfile/run.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2019-2020 VMware, Inc. All Rights Reserved. +# Copyright (c) 2019-2021 VMware, Inc. All Rights Reserved. # SPDX-License-Identifier: BSD-2-Clause """ @@ -123,7 +123,7 @@ def full_image_analysis(dfile, options): """This subroutine is executed when a Dockerfile is successfully built""" image_list = [] # attempt to load the built image metadata - full_image = cimage.load_full_image(dfile) + full_image = cimage.load_full_image(dfile, image_type='docker') if full_image.origins.is_empty(): # Add an image origin here full_image.origins.add_notice_origin(