import logging import features from app import secscan_endpoint from flask import request, make_response, Blueprint from data import model from data.database import (RepositoryNotification, Repository, ExternalNotificationEvent, RepositoryTag, Image, ImageStorage) from endpoints.common import route_show_if from endpoints.notificationhelper import spawn_notification from collections import defaultdict logger = logging.getLogger(__name__) secscan = Blueprint('secscan', __name__) @route_show_if(features.SECURITY_SCANNER) @secscan.route('/notification', methods=['POST']) def secscan_notification(): data = request.get_json() logger.debug('Got notification from Clair: %s', data) # Find all tags that contain the layer(s) introducing the vulnerability. content = data['Content'] layer_ids = content.get('NewIntroducingLayersIDs', content.get('IntroducingLayersIDs', [])) if not layer_ids: return make_response('Okay') # TODO(jzelinkskie): Write a queueitem for these layer ids, and do the rest of this # in a worker. cve_id = data['Name'] vulnerability = data['Content']['Vulnerability'] priority = vulnerability['Priority'] # Lookup the external event for when we have vulnerabilities. event = ExternalNotificationEvent.get(name='vulnerability_found') # For each layer, retrieving the matching tags and join with repository to determine which # require new notifications. tag_map = defaultdict(set) repository_map = {} for layer_id in layer_ids: (docker_image_id, storage_uuid) = layer_id.split('.', 2) tags = model.tag.get_matching_tags(docker_image_id, storage_uuid, RepositoryTag, Repository, Image, ImageStorage) # Additionally filter to tags only in repositories that have the event setup. matching = (tags.switch(RepositoryTag) .join(Repository) .join(RepositoryNotification) .where(RepositoryNotification.event == event)) check_map = {} for tag in matching: # Verify that the tag's root image has the vulnerability. tag_layer_id = '%s.%s' % (tag.image.docker_image_id, tag.image.storage.uuid) logger.debug('Checking if layer %s is vulnerable to %s', tag_layer_id, cve_id) if not tag_layer_id in check_map: is_vulerable = secscan_endpoint.check_layer_vulnerable(tag_layer_id, cve_id) check_map[tag_layer_id] = is_vulerable logger.debug('Result of layer %s is vulnerable to %s check: %s', tag_layer_id, cve_id, check_map[tag_layer_id]) if check_map[tag_layer_id]: # Add the vulnerable tag to the list. tag_map[tag.repository_id].add(tag.name) repository_map[tag.repository_id] = tag.repository # For each of the tags found, issue a notification. for repository_id in tag_map: tags = tag_map[repository_id] event_data = { 'tags': list(tags), 'vulnerability': { 'id': data['Name'], 'description': vulnerability['Description'], 'link': vulnerability['Link'], 'priority': priority, }, } # TODO: only add this notification if the repository's event(s) defined meet the priority # minimum. spawn_notification(repository_map[repository_id], 'vulnerability_found', event_data) return make_response('Okay')