def delete_image(user_id, image_id): """ DELETE the image and all resources for it. Returns 204 - No Content on success :param user_id: :param image_id: :return: """ db = get_session() try: log.info( "Deleting image {}/{} and all associated resources".format( user_id, image_id ) ) img = db.query(Image).get((image_id, user_id)) if img: get_vulnerabilities_provider().delete_image_vulnerabilities( image=img, db_session=db ) try: conn_timeout = ApiRequestContextProxy.get_service().configuration.get( "catalog_client_conn_timeout", DEFAULT_CACHE_CONN_TIMEOUT ) read_timeout = ApiRequestContextProxy.get_service().configuration.get( "catalog_client_read_timeout", DEFAULT_CACHE_READ_TIMEOUT ) mgr = EvaluationCacheManager( img, None, None, conn_timeout, read_timeout ) mgr.flush() except Exception as ex: log.exception( "Could not delete evaluations for image {}/{} in the cache. May be orphaned".format( user_id, image_id ) ) db.delete(img) db.commit() else: db.rollback() # Idempotently return 204. This isn't properly RESTY, but idempotency on delete makes clients much cleaner. return None, 204 except HTTPException: raise except Exception as e: log.exception( "Error processing DELETE request for image {}/{}".format(user_id, image_id) ) db.rollback() return ( make_response_error( "Error deleting image {}/{}: {}".format(user_id, image_id, e), in_httpcode=500, ), 500, )
def ping(): """ GET / :return: 200 status with api version string """ return ApiRequestContextProxy.get_service().__service_api_version__, 200
def create_object(bucket, archiveid, bodycontent): httpcode = 500 try: account_name = ApiRequestContextProxy.namespace() obj_mgr = anchore_engine.subsys.object_store.manager.get_manager() jsonbytes = anchore_utils.ensure_bytes(json.dumps(bodycontent)) rc = obj_mgr.put(account_name, bucket, archiveid, jsonbytes) my_svc = ApiRequestContextProxy.get_service() if my_svc is not None: resource_url = my_svc.service_record[ 'base_url'] + "/" + my_svc.service_record[ 'version'] + "/archive/" + bucket + "/" + archiveid else: resource_url = "N/A" return_object = resource_url httpcode = 200 except Exception as err: return_object = anchore_engine.common.helpers.make_response_error( err, in_httpcode=httpcode) return return_object, httpcode
def validate_schema(notification): """ Check if the notification conforms to the Schema outlined in the Swagger Spec. Also only do this for the types we know (policy_eval, vuln_update, tag_update, analysis_update) :param notification: notification object to deliver """ ret = False notification_type = notification.get("data", {}).get("notification_type", None) if notification_type not in NOTIFICATION_MAPPING.keys(): logger.debug( "Not doing Schema validation for Notification Type: {}".format( notification_type)) return ret elif not notification_type: logger.warn("Notification Type not resolved: {}".format(notification)) return ret notification_schema_definition = NOTIFICATION_MAPPING.get( notification_type, "NotificationBase") spec = ApiRequestContextProxy.get_service().api_spec schema = spec.get("definitions", {}).get(notification_schema_definition) try: jsonschema.validate(notification, schema) ret = True except jsonschema.ValidationError as e: logger.error( "Notification does not pass validation, still delivering for backwards compatibility: {}" .format(e)) ret = False return ret
def create_archive(bucket, archiveid, bodycontent): httpcode = 500 try: accountName = ApiRequestContextProxy.namespace() archive_sys = archive.get_manager() try: jsonbytes = anchore_utils.ensure_bytes(json.dumps(bodycontent)) my_svc = ApiRequestContextProxy.get_service() if my_svc is not None: resource_url = (my_svc.service_record["base_url"] + "/" + my_svc.service_record["version"] + "/archive/" + bucket + "/" + archiveid) else: resource_url = "N/A" rc = archive_sys.put(accountName, bucket, archiveid, jsonbytes) return_object = resource_url httpcode = 200 except Exception as err: httpcode = 500 raise err except Exception as err: return_object = anchore_engine.common.helpers.make_response_error( err, in_httpcode=httpcode) return return_object, httpcode
def action_provider(op_id): """ Lazy lookup of the action associated with the operation id via the request context reference to the parent service, which provides the map of ops->actions (via the swagger doc) :param op_id: :return: """ return ApiRequestContextProxy.get_service().action_for_operation(op_id)
def get_image_content(image_digest, content_type): httpcode = 500 try: return_object = ApiRequestContextProxy.get_service().get_image_content( ApiRequestContextProxy.namespace(), content_type, image_digest) httpcode = 200 except Exception as err: logger.exception("Failed to lookup image content") return_object = make_response_error(err, in_httpcode=httpcode) httpcode = return_object["httpcode"] return return_object, httpcode
def get_oauth_token(grant_type="password", username=None, password=None, client_id="anonymous"): """ POST /oauth/token Requires the resource-owners credentials in the Authorization Header. This is a bit of a mix of the ResourceOwnerPasswordGrant flow and the ImplicitGrant flow since this function will populate the necessary fields to perform a password grant if the Authorization header is set and no content body is provided Note: the parameters above are embedded within the connexion request object, but must be specified in the method signature in order for connexion to route the request to this method. So it may appear that they are unused, but have no fear, they are! :return: """ # Short-circuit if no oauth/token configured try: tok_mgr = token_manager() authz = ApiRequestContextProxy.get_service()._oauth_app except Exception as e: raise AccessDeniedError("Oauth not enabled in configuration", detail={}) # Add some default properties if not set in the request try: if request.content_length == 0 or not request.form: logger.debug( "Handling converting empty body into form-based grant request") if not request.data and not request.form: setattr( request, "form", ImmutableMultiDict([ ("username", request.authorization.username), ("password", request.authorization.password), ("grant_type", "password"), ("client_id", "anonymous"), ]), ) resp = authz.create_token_response() logger.debug("Token resp: {}".format(resp)) return resp except: logger.debug_exception("Error authenticating") raise
def ingress_image(ingress_request): """ :param ingress_request json object specifying the identity of the image to sync :return: status result for image load """ req = ImageIngressRequest.from_json(ingress_request) if not req.user_id: raise ValueError("user_id") if not req.image_id: raise ValueError("image_id") try: # Try this synchronously for now to see how slow it really is conn_timeout = ApiRequestContextProxy.get_service().configuration.get( "catalog_client_conn_timeout", DEFAULT_CACHE_CONN_TIMEOUT ) read_timeout = ApiRequestContextProxy.get_service().configuration.get( "catalog_client_read_timeout", DEFAULT_CACHE_READ_TIMEOUT ) t = ImageLoadTask( req.user_id, req.image_id, url=req.fetch_url, content_conn_timeout=conn_timeout, content_read_timeout=read_timeout, ) result = t.execute() resp = ImageIngressResponse() if not result: resp.status = "loaded" else: # We're doing a sync call above, so just send loaded. It should be 'accepted' once async works. resp.status = "loaded" resp.problems = list() return resp.to_json(), 200 except Exception as e: log.exception("Error loading image into policy engine") return make_response_error(e, in_httpcode=500), 500
def create_object_in_storage(bucket: str, archiveid: str, bodycontent: str, is_json: bool = False): """ Creates an object in storeage with the object storage manager. Takes param to determine if it should be stored as json or not. Used by two endpoints so that a single endpoint does not return multiple different content types """ http_code = 500 try: account_name = ApiRequestContextProxy.namespace() obj_mgr = anchore_engine.subsys.object_store.manager.get_manager() if is_json: bodycontent = anchore_utils.ensure_bytes(json.dumps(bodycontent)) obj_mgr.put(account_name, bucket, archiveid, bodycontent) except Exception as err: return_object = anchore_engine.common.helpers.make_response_error( err, in_httpcode=http_code) return return_object, http_code my_svc = ApiRequestContextProxy.get_service() if my_svc is None: resource_url = "N/A" else: try: path_parts = [ my_svc.service_record["base_url"], my_svc.service_record["version"], "archive", bucket, archiveid, ] except KeyError as err: return_object = anchore_engine.common.helpers.make_response_error( err, in_httpcode=http_code) return return_object, http_code resource_url = "/".join(path_parts) return_object = resource_url http_code = 200 return return_object, http_code
def get_oauth_token(): """ POST /oauth/token Requires the resource-owners credentials in the Authorization Header. This is a bit of a mix of the ResourceOwnerPasswordGrant flow and the ImplicitGrant flow since this function will populate the necessary fields to perform a password grant if the Authorization header is set and no content body is provided :return: """ # Short-circuit if no oauth/token configured try: tok_mgr = token_manager() authz = ApiRequestContextProxy.get_service()._oauth_app except Exception as e: raise AccessDeniedError('Oauth not enabled in configuration', detail={}) # Add some default properties if not set in the request try: if request.content_length == 0 or not request.form: logger.debug( 'Handling converting empty body into form-based grant request') if not request.data and not request.form: setattr( request, 'form', ImmutableMultiDict([ ('username', request.authorization.username), ('password', request.authorization.password), ('grant_type', 'password'), ('client_id', 'anonymous') ])) resp = authz.create_token_response() logger.debug('Token resp: {}'.format(resp)) return resp except: logger.debug_exception('Error authenticating') raise
def check_user_image_inline(user_id, image_id, tag, bundle): """ Execute a policy evaluation using the info in the request body including the bundle content :param user_id: :param image_id: :param tag: :param bundle: :return: """ timer = time.time() db = get_session() cache_mgr = None try: # Input validation if tag is None: # set tag value to a value that only matches wildcards tag = "*/*:*" try: img_obj = db.query(Image).get((image_id, user_id)) except: return make_response_error("Image not found", in_httpcode=404), 404 if not img_obj: log.info( "Request for evaluation of image that cannot be found: user_id = {}, image_id = {}" .format(user_id, image_id)) return make_response_error("Image not found", in_httpcode=404), 404 if evaluation_cache_enabled: timer2 = time.time() try: try: conn_timeout = ( ApiRequestContextProxy.get_service().configuration.get( "catalog_client_conn_timeout", DEFAULT_CACHE_CONN_TIMEOUT)) read_timeout = ( ApiRequestContextProxy.get_service().configuration.get( "catalog_client_read_timeout", DEFAULT_CACHE_READ_TIMEOUT)) cache_mgr = EvaluationCacheManager(img_obj, tag, bundle, conn_timeout, read_timeout) except ValueError as err: log.warn( "Could not leverage cache due to error in bundle data: {}" .format(err)) cache_mgr = None if cache_mgr is None: log.info( "Could not initialize cache manager for policy evaluation, skipping cache usage" ) else: cached_result = cache_mgr.refresh() if cached_result: metrics.counter_inc( name="anchore_policy_evaluation_cache_hits") metrics.histogram_observe( "anchore_policy_evaluation_cache_access_latency", time.time() - timer2, status="hit", ) log.info( "Returning cached result of policy evaluation for {}/{}, with tag {} and bundle {} with digest {}. Last evaluation: {}" .format( user_id, image_id, tag, cache_mgr.bundle_id, cache_mgr.bundle_digest, cached_result.get("last_modified"), )) return cached_result else: metrics.counter_inc( name="anchore_policy_evaluation_cache_misses") metrics.histogram_observe( "anchore_policy_evaluation_cache_access_latency", time.time() - timer2, status="miss", ) log.info( "Policy evaluation not cached, or invalid, executing evaluation for {}/{} with tag {} and bundle {} with digest {}" .format( user_id, image_id, tag, cache_mgr.bundle_id, cache_mgr.bundle_digest, )) except Exception as ex: log.exception( "Unexpected error operating on policy evaluation cache. Skipping use of cache." ) else: log.info("Policy evaluation cache disabled. Executing evaluation") # Build bundle exec. problems = [] executable_bundle = None try: # Allow deprecated gates here to support upgrade cases from old policy bundles. executable_bundle = build_bundle(bundle, for_tag=tag, allow_deprecated=True) if executable_bundle.init_errors: problems = executable_bundle.init_errors except InitializationError as e: log.exception( "Bundle construction and initialization returned errors") problems = e.causes eval_result = None if not problems: # Execute bundle try: eval_result = executable_bundle.execute( img_obj, tag, ExecutionContext(db_session=db, configuration={})) except Exception as e: log.exception( "Error executing policy bundle {} against image {} w/tag {}: {}" .format(bundle["id"], image_id, tag, e)) return ( make_response_error( "Internal bundle evaluation error", details={ "message": "Cannot execute given policy against the image due to errors executing the policy bundle: {}" .format(e) }, in_httpcode=500, ), 500, ) else: # Construct a failure eval with details on the errors and mappings to send to client eval_result = build_empty_error_execution(img_obj, tag, executable_bundle, errors=problems, warnings=[]) if (executable_bundle and executable_bundle.mapping and len(executable_bundle.mapping.mapping_rules) == 1): eval_result.executed_mapping = executable_bundle.mapping.mapping_rules[ 0] resp = PolicyEvaluation() resp.user_id = user_id resp.image_id = image_id resp.tag = tag resp.bundle = bundle resp.matched_mapping_rule = (eval_result.executed_mapping.json() if eval_result.executed_mapping else False) resp.last_modified = int(time.time()) resp.final_action = eval_result.bundle_decision.final_decision.name resp.final_action_reason = eval_result.bundle_decision.reason resp.matched_whitelisted_images_rule = ( eval_result.bundle_decision.whitelisted_image.json() if eval_result.bundle_decision.whitelisted_image else False) resp.matched_blacklisted_images_rule = ( eval_result.bundle_decision.blacklisted_image.json() if eval_result.bundle_decision.blacklisted_image else False) resp.result = eval_result.as_table_json() resp.created_at = int(time.time()) resp.evaluation_problems = [ problem_from_exception(i) for i in eval_result.errors ] resp.evaluation_problems += [ problem_from_exception(i) for i in eval_result.warnings ] if resp.evaluation_problems: for i in resp.evaluation_problems: log.warn( "Returning evaluation response for image {}/{} w/tag {} and bundle {} that contains error: {}" .format(user_id, image_id, tag, bundle["id"], json.dumps(i.to_json()))) metrics.histogram_observe( "anchore_policy_evaluation_time_seconds", time.time() - timer, status="fail", ) else: metrics.histogram_observe( "anchore_policy_evaluation_time_seconds", time.time() - timer, status="success", ) result = resp.to_json() # Never let the cache block returning results try: if evaluation_cache_enabled and cache_mgr is not None: cache_mgr.save(result) except Exception as ex: log.exception( "Failed saving policy result in cache. Skipping and continuing." ) db.commit() return result except HTTPException as e: db.rollback() log.exception("Caught exception in execution: {}".format(e)) raise except Exception as e: db.rollback() log.exception("Failed processing bundle evaluation: {}".format(e)) return ( make_response_error( "Unexpected internal error", details={"message": str(e)}, in_httpcode=500, ), 500, ) finally: db.close()
def version_check(): """ :return: """ return version_response(ApiRequestContextProxy.get_service().versions), 200