def get_image_manifest_skopeo(url, registry, repo, intag=None, indigest=None, topdigest=None, user=None, pw=None, verify=True, topmanifest=None):
    manifest = {}
    digest = None
    testDigest = None

    if indigest:
        pullstring = registry + "/" + repo + "@" + indigest
    elif intag:
        pullstring = registry + "/" + repo + ":" + intag
    else:
        raise Exception("invalid input - must supply either an intag or indigest")

    try:
        try:
            rawmanifest = get_image_manifest_skopeo_raw(pullstring, user=user, pw=pw, verify=verify)
            digest = manifest_to_digest(rawmanifest)
            manifest = json.loads(rawmanifest)
            if topmanifest is None:
                topmanifest = json.loads(rawmanifest)
            if not topdigest:
                topdigest = digest

            if manifest.get('schemaVersion') == 2 and manifest.get('mediaType') == 'application/vnd.docker.distribution.manifest.list.v2+json':
                # Get the arch-specific version for amd64 and linux
                new_digest = None
                for entry in manifest.get('manifests'):
                    platform = entry.get('platform')
                    if platform and platform.get('architecture') in ['amd64'] and platform.get('os') in ['linux', 'windows']:
                        new_digest = entry.get('digest')
                        break

                return get_image_manifest_skopeo(url=url, registry=registry, repo=repo, intag=None, indigest=new_digest, user=user, pw=pw, verify=verify, topdigest=topdigest, topmanifest=topmanifest)
        except Exception as err:
            logger.warn("CMD failed - exception: " + str(err))
            raise err

    except Exception as err:
        import traceback
        traceback.print_exc()
        raise err

    if not manifest or not digest:
        raise SkopeoError(msg="No digest/manifest from skopeo")

    return(manifest, digest, topdigest, topmanifest)
Exemple #2
0
def exec(docker_archive, anchore_archive, digest, parent_digest, image_id, tag,
         account_id, manifest, dockerfile, created_at, annotation):
    """
    Analyze a local image stored as a docker archive (output result of 'docker save'), and generate an anchore image archive tarball ready for import into an anchore engine.

    DOCKER_ARCHIVE : Location of input docker archive tarfile to analyze
    ANCHORE_ARCHIVE : Location of output anchore image archive to write

    """

    global config

    # this could be improved to allow use to input timestamps (created_at, analyzed_at, etc)
    now = int(time.time())
    try:
        try:
            imageDigest = None
            input_manifest_data = None
            rawmanifest = None

            if (not manifest and not digest) or (manifest and digest):
                raise Exception(
                    "must supply either an image digest or a valid manifest, but not both"
                )

            if os.path.exists(anchore_archive):
                raise Exception(
                    "the supplied anchore archive file ({}) already exists, please remove and try again"
                    .format(anchore_archive))

            if manifest:
                try:
                    with open(manifest, 'r') as FH:
                        # TODO implement manifest validator for anchore requirements, specifically
                        rawmanifest = FH.read()
                        input_manifest_data = json.loads(rawmanifest)
                        imageDigest = manifest_to_digest(rawmanifest)
                except Exception as err:
                    raise ValueError(
                        "cannot calculate digest from supplied manifest - exception: {}"
                        .format(err))

            if digest:
                if re.match("^sha256:[\d|a-f]{64}$", digest):
                    imageDigest = digest
                else:
                    raise ValueError(
                        "input digest does not validate - must be sha256:<64 hex characters>"
                    )

            if parent_digest:
                if re.match("^sha256:[\d|a-f]{64}$", parent_digest):
                    parentDigest = parent_digest
                else:
                    raise ValueError(
                        "input parent_digest does not validate - must be sha256:<64 hex characters>"
                    )
            else:
                parentDigest = imageDigest

            if image_id:
                if re.match("^[\d|a-f]{64}$", image_id):
                    imageId = image_id
                else:
                    raise ValueError("input image_id does not validate")
            else:
                # TODO this could be improved to generate imageId from configuration hash
                imageId = "{}".format(''.join(
                    [random.choice('0123456789abcdef') for x in range(0, 64)]))

            if account_id:
                userId = account_id
            else:
                userId = 'admin'

            if created_at:
                try:
                    if int(created_at) < 0 or int(created_at) > now + 1:
                        raise Exception()
                except Exception as err:
                    raise ValueError(
                        "created_at must by a unix timestamp between 0 and now ({})"
                        .format(now))
            else:
                created_at = now

            try:
                inputTag = tag
                image_info = parse_dockerimage_string(inputTag)
                fulltag = "{}/{}:{}".format(image_info['registry'],
                                            image_info['repo'],
                                            image_info['tag'])
                fulldigest = "{}/{}@{}".format(image_info['registry'],
                                               image_info['repo'], imageDigest)
            except Exception as err:
                raise ValueError(
                    "input tag does not validate - exception: {}".format(err))

            dockerfile_mode = "Guessed"
            dockerfile_contents = None
            if dockerfile:
                with open(dockerfile, 'r') as FH:
                    dockerfile_contents = ensure_str(
                        base64.b64encode(ensure_bytes(FH.read())))
                    dockerfile_mode = "Actual"

            annotations = {}
            if annotation:
                for a in annotation:
                    try:
                        (k, v) = a.split('=', 1)
                        if k and v:
                            annotations[k] = v
                        else:
                            raise Exception("found null in key or value")
                    except Exception as err:
                        raise ValueError(
                            "annotation format error - annotations must be of the form (--annotation key=value), found: {}"
                            .format(a))

            workspace_root = config['tmp_dir']
        except Exception as err:
            # input setup/validation failure
            raise err

        logger.debug(
            "input has been prepared: imageDigest={} parentDigest={} imageId={} inputTag={} fulltag={} fulldigest={} userId={} annotations={} created_at={}"
            .format(imageDigest, parentDigest, imageId, inputTag, fulltag,
                    fulldigest, userId, annotations, created_at))

        # create an image record
        try:
            image_record = make_image_record(userId,
                                             'docker',
                                             None,
                                             image_metadata={
                                                 'tag': fulltag,
                                                 'digest': fulldigest,
                                                 'imageId': imageId,
                                                 'parentdigest': parentDigest,
                                                 'created_at': created_at,
                                                 'dockerfile':
                                                 dockerfile_contents,
                                                 'dockerfile_mode':
                                                 dockerfile_mode,
                                                 'annotations': annotations
                                             },
                                             registry_lookup=False,
                                             registry_creds=(None, None))
            image_record['created_at'] = created_at
            image_record['last_updated'] = created_at
            image_record['analyzed_at'] = now
            image_record['analysis_status'] = 'analyzed'
            image_record['image_status'] = 'active'
            image_record['record_state_key'] = 'active'
            for image_detail in image_record['image_detail']:
                image_detail['created_at'] = created_at
                image_detail['last_updated'] = created_at
                image_detail['tag_detected_at'] = created_at
                image_detail['record_state_key'] = 'active'
        except Exception as err:
            # image record setup fail
            raise err

        # perform analysis
        try:
            image_data, analyzed_manifest_data = analyze_image(
                userId,
                rawmanifest,
                image_record,
                workspace_root,
                config,
                registry_creds=[],
                use_cache_dir=None,
                image_source='docker-archive',
                image_source_meta=docker_archive)

            image_content_data = {}
            for content_type in anchore_engine.common.image_content_types + anchore_engine.common.image_metadata_types:
                try:
                    image_content_data[
                        content_type] = anchore_engine.common.helpers.extract_analyzer_content(
                            image_data,
                            content_type,
                            manifest=input_manifest_data)
                except:
                    image_content_data[content_type] = {}

            anchore_engine.common.helpers.update_image_record_with_analysis_data(
                image_record, image_data)
            image_record['image_size'] = int(image_record['image_size'])
        except Exception as err:
            # image analysis fail
            raise err

        # generate an output image archive tarball
        archive_file = anchore_archive
        try:
            with ImageArchive.for_writing(archive_file) as img_archive:

                img_archive.account = userId
                img_archive.image_digest = imageDigest
                img_archive.manifest.metadata = {
                    'versions': localconfig.get_versions(),
                    'image_id': imageId,
                    'image_record': json.dumps(image_record, sort_keys=True)
                }

                pack_data = {'document': image_data}
                data = ensure_bytes(json.dumps(pack_data, sort_keys=True))
                img_archive.add_artifact('analysis',
                                         source=ObjectStoreLocation(
                                             bucket='analysis_data',
                                             key=imageDigest),
                                         data=data,
                                         metadata=None)

                pack_data = {'document': image_content_data}
                data = ensure_bytes(json.dumps(pack_data, sort_keys=True))
                img_archive.add_artifact('image_content',
                                         source=ObjectStoreLocation(
                                             bucket='image_content_data',
                                             key=imageDigest),
                                         data=data,
                                         metadata=None)

                pack_data = {'document': input_manifest_data}
                data = ensure_bytes(json.dumps(pack_data, sort_keys=True))
                img_archive.add_artifact('image_manifest',
                                         source=ObjectStoreLocation(
                                             bucket='manifest_data',
                                             key=imageDigest),
                                         data=data,
                                         metadata=None)
        except Exception as err:
            # archive tarball generate fail
            raise err

    except Exception as err:
        logger.error(
            anchore_manager.cli.utils.format_error_output(
                click_config, 'db', {}, err))
        sys.exit(2)

    click.echo(
        "Analysis complete for image {} - archive file is located at {}".
        format(imageDigest, archive_file))
def get_image_manifest_skopeo(url, registry, repo, intag=None, indigest=None, user=None, pw=None, verify=True):
    manifest = {}
    digest = None
    testDigest = None

    if indigest:
        pullstring = registry + "/" + repo + "@" + indigest
    elif intag:
        pullstring = registry + "/" + repo + ":" + intag
    else:
        raise Exception("invalid input - must supply either an intag or indigest")

    try:
        proc_env = os.environ.copy()
        if user and pw:
            proc_env['SKOPUSER'] = user
            proc_env['SKOPPASS'] = pw
            credstr = '--creds \"${SKOPUSER}\":\"${SKOPPASS}\"'
        else:
            credstr = ""

        if verify:
            tlsverifystr = "--tls-verify=true"
        else:
            tlsverifystr = "--tls-verify=false"
            
        try:
            cmd = ["/bin/sh", "-c", "skopeo inspect --raw {} {} docker://{}".format(tlsverifystr, credstr, pullstring)]
            cmdstr = ' '.join(cmd)
            try:
                rc, sout, serr = run_command_list(cmd, env=proc_env)
                if rc != 0:
                    raise SkopeoError(cmd=cmd, rc=rc, out=sout, err=serr)
                else:
                    logger.debug("command succeeded: cmd="+str(cmdstr)+" stdout="+str(sout).strip()+" stderr="+str(serr).strip())
            except Exception as err:
                logger.error("command failed with exception - " + str(err))
                raise err

            sout = str(sout, 'utf-8') if sout else None
            digest = manifest_to_digest(sout)
            manifest = json.loads(sout)

            if manifest.get('schemaVersion') == 2 and manifest.get('mediaType') == 'application/vnd.docker.distribution.manifest.list.v2+json':
                # Get the arch-specific version for amd64 and linux
                new_digest = None
                for entry in manifest.get('manifests'):
                    platform = entry.get('platform')
                    if platform and platform.get('architecture') in ['amd64'] and platform.get('os') == 'linux':
                        new_digest = entry.get('digest')
                        break

                return get_image_manifest_skopeo(url=url, registry=registry, repo=repo, intag=None, indigest=new_digest, user=user, pw=pw, verify=verify)
        except Exception as err:
            logger.warn("CMD failed - exception: " + str(err))
            raise err

    except Exception as err:
        import traceback
        traceback.print_exc()
        raise err

    if not manifest or not digest:
        raise SkopeoError(msg="No digest/manifest from skopeo")

    return(manifest, digest)