Merge pull request #2397 from coreos-inc/qss-initial-notify
Batch QSS notifications after initial scan
This commit is contained in:
commit
4a90ebb901
3 changed files with 67 additions and 21 deletions
|
@ -113,13 +113,14 @@ def _build_summary(event_data):
|
|||
class VulnerabilityFoundEvent(NotificationEvent):
|
||||
CONFIG_LEVEL = 'level'
|
||||
VULNERABILITY_KEY = 'vulnerability'
|
||||
MULTIPLE_VULNERABILITY_KEY = 'vulnerabilities'
|
||||
|
||||
@classmethod
|
||||
def event_name(cls):
|
||||
return 'vulnerability_found'
|
||||
|
||||
def get_level(self, event_data, notification_data):
|
||||
priority = event_data['vulnerability']['priority']
|
||||
priority = event_data[VulnerabilityFoundEvent.CONFIG_LEVEL]['priority']
|
||||
if priority == 'Defcon1' or priority == 'Critical':
|
||||
return 'error'
|
||||
|
||||
|
@ -166,9 +167,21 @@ class VulnerabilityFoundEvent(NotificationEvent):
|
|||
return actual_level_index <= filter_level_index
|
||||
|
||||
def get_summary(self, event_data, notification_data):
|
||||
msg = '%s vulnerability detected in repository %s in %s tags'
|
||||
return msg % (event_data['vulnerability']['priority'], event_data['repository'],
|
||||
len(event_data['tags']))
|
||||
multiple_vulns = event_data.get(VulnerabilityFoundEvent.MULTIPLE_VULNERABILITY_KEY)
|
||||
if multiple_vulns is not None:
|
||||
top_priority = multiple_vulns[0].get('priority', 'Unknown')
|
||||
matching = [v for v in multiple_vulns if v.get('priority', 'Unknown') == top_priority]
|
||||
|
||||
msg = '%s %s' % (len(matching), top_priority)
|
||||
if len(matching) < len(multiple_vulns):
|
||||
msg += ' and %s more' % (len(multiple_vulns) - len(matching))
|
||||
|
||||
msg += ' vulnerabilities were detected in repository %s in %s tags'
|
||||
return msg % (event_data['repository'], len(event_data['tags']))
|
||||
else:
|
||||
msg = '%s vulnerability detected in repository %s in %s tags'
|
||||
return msg % (event_data['vulnerability']['priority'], event_data['repository'],
|
||||
len(event_data['tags']))
|
||||
|
||||
|
||||
class BaseBuildEvent(NotificationEvent):
|
||||
|
|
|
@ -317,6 +317,15 @@ class TestSecurityScanner(unittest.TestCase):
|
|||
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
|
||||
"Severity": "Low",
|
||||
"FixedBy": "9.23-5"
|
||||
},
|
||||
|
||||
{
|
||||
"Name": "CVE-2016-7530",
|
||||
"Namespace": "debian:8",
|
||||
"Description": "Some other service",
|
||||
"Link": "https://security-tracker.debian.org/tracker/CVE-2016-7530",
|
||||
"Severity": "Unknown",
|
||||
"FixedBy": "19.343-2"
|
||||
}
|
||||
])
|
||||
|
||||
|
@ -338,6 +347,14 @@ class TestSecurityScanner(unittest.TestCase):
|
|||
self.assertEquals('CVE-2014-9471', body['event_data']['vulnerability']['id'])
|
||||
self.assertEquals('Low', body['event_data']['vulnerability']['priority'])
|
||||
self.assertTrue(body['event_data']['vulnerability']['has_fix'])
|
||||
|
||||
self.assertEquals('CVE-2014-9471', body['event_data']['vulnerabilities'][0]['id'])
|
||||
self.assertEquals(2, len(body['event_data']['vulnerabilities']))
|
||||
|
||||
# Ensure we get the correct event message out as well.
|
||||
event = VulnerabilityFoundEvent()
|
||||
msg = '1 Low and 1 more vulnerabilities were detected in repository devtable/simple in 2 tags'
|
||||
self.assertEquals(msg, event.get_summary(body['event_data'], {}))
|
||||
else:
|
||||
self.assertIsNone(queue_item)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from endpoints.notificationhelper import spawn_notification
|
|||
from data.database import ExternalNotificationEvent, IMAGE_NOT_SCANNED_ENGINE_VERSION, Image
|
||||
from data.model.tag import filter_tags_have_repository_event, get_tags_for_image
|
||||
from data.model.image import set_secscan_status, get_image_with_storage_and_parent_base
|
||||
from util.secscan import PRIORITY_LEVELS
|
||||
from util.secscan.api import (APIRequestFailure, AnalyzeLayerException, MissingParentLayerException,
|
||||
InvalidLayerException, AnalyzeLayerRetryException)
|
||||
from util.morecollections import AttrDict
|
||||
|
@ -150,30 +151,45 @@ class LayerAnalyzer(object):
|
|||
found_features = layer_data['Layer'].get('Features', [])
|
||||
for repository_id in repository_map:
|
||||
tags = repository_map[repository_id]
|
||||
vulnerabilities = dict()
|
||||
|
||||
# Collect all the vulnerabilities found for the layer under each repository and send
|
||||
# as a batch notification.
|
||||
for feature in found_features:
|
||||
if 'Vulnerabilities' not in feature:
|
||||
continue
|
||||
|
||||
for vulnerability in feature.get('Vulnerabilities', []):
|
||||
event_data = {
|
||||
'tags': [tag.name for tag in tags],
|
||||
'vulnerability': {
|
||||
'id': vulnerability['Name'],
|
||||
'description': vulnerability.get('Description', None),
|
||||
'link': vulnerability.get('Link', None),
|
||||
'has_fix': 'FixedBy' in vulnerability,
|
||||
vuln_data = {
|
||||
'id': vulnerability['Name'],
|
||||
'description': vulnerability.get('Description', None),
|
||||
'link': vulnerability.get('Link', None),
|
||||
'has_fix': 'FixedBy' in vulnerability,
|
||||
|
||||
# TODO: Change this key name if/when we change the event format.
|
||||
'priority': vulnerability.get('Severity', 'Unknown'),
|
||||
},
|
||||
# TODO: Change this key name if/when we change the event format.
|
||||
'priority': vulnerability.get('Severity', 'Unknown'),
|
||||
}
|
||||
|
||||
# TODO(jzelinskie): remove when more endpoints have been converted to using
|
||||
# interfaces
|
||||
repository = AttrDict({
|
||||
'namespace_name': tags[0].repository.namespace_user.username,
|
||||
'name': tags[0].repository.name,
|
||||
})
|
||||
vulnerabilities[vulnerability['Name']] = vuln_data
|
||||
|
||||
spawn_notification(repository, 'vulnerability_found', event_data)
|
||||
# TODO(jzelinskie): remove when more endpoints have been converted to using
|
||||
# interfaces
|
||||
repository = AttrDict({
|
||||
'namespace_name': tags[0].repository.namespace_user.username,
|
||||
'name': tags[0].repository.name,
|
||||
})
|
||||
|
||||
repo_vulnerabilities = list(vulnerabilities.values())
|
||||
if not repo_vulnerabilities:
|
||||
continue
|
||||
|
||||
priority_key = lambda v: PRIORITY_LEVELS.get(v['priority'], {}).get('index', 100)
|
||||
repo_vulnerabilities.sort(key=priority_key)
|
||||
|
||||
event_data = {
|
||||
'tags': [tag.name for tag in tags],
|
||||
'vulnerabilities': repo_vulnerabilities,
|
||||
'vulnerability': repo_vulnerabilities[0], # For back-compat with existing events.
|
||||
}
|
||||
|
||||
spawn_notification(repository, 'vulnerability_found', event_data)
|
||||
|
|
Reference in a new issue