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)
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)