def assert_analyze_layer_notify(self, security_indexed_engine, security_indexed, expect_notification): layer = model.tag.get_tag_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest", include_storage=True) self.assertFalse(layer.security_indexed) self.assertEquals(-1, layer.security_indexed_engine) # Ensure there are no existing events. self.assertIsNone(notification_queue.get()) # Add a repo event for the layer. repo = model.repository.get_repository(ADMIN_ACCESS_USER, SIMPLE_REPO) model.notification.create_repo_notification(repo, "vulnerability_found", "quay_notification", {}, {"level": 100}) # Update the layer's state before analyzing. layer.security_indexed_engine = security_indexed_engine layer.security_indexed = security_indexed layer.save() with fake_security_scanner() as security_scanner: security_scanner.set_vulns( security_scanner.layer_id(layer), [ { "Name": "CVE-2014-9471", "Namespace": "debian:8", "Description": "Some service", "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", }, ], ) analyzer = LayerAnalyzer(app.config, self.api) analyzer.analyze_recursively(layer) layer = model.tag.get_tag_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest") self.assertAnalyzed(layer, security_scanner, True, 1) # Ensure an event was written for the tag (if necessary). time.sleep(1) queue_item = notification_queue.get() if expect_notification: self.assertIsNotNone(queue_item) body = json.loads(queue_item.body) self.assertEquals(set(["latest", "prod"]), set(body["event_data"]["tags"])) 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"], {})) self.assertEquals("info", event.get_level(body["event_data"], {})) else: self.assertIsNone(queue_item) # Ensure its security indexed engine was updated. updated_layer = model.tag.get_tag_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest") self.assertEquals(updated_layer.id, layer.id) self.assertTrue(updated_layer.security_indexed_engine > 0)
def assert_notification_worker_offset_pages(self, indexed=False): layer1 = _get_legacy_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest", include_storage=True) layer2 = _get_legacy_image(ADMIN_ACCESS_USER, COMPLEX_REPO, "prod", include_storage=True) # Add a repo events for the layers. simple_repo = model.repository.get_repository(ADMIN_ACCESS_USER, SIMPLE_REPO) complex_repo = model.repository.get_repository(ADMIN_ACCESS_USER, COMPLEX_REPO) model.notification.create_repo_notification(simple_repo, "vulnerability_found", "quay_notification", {}, {"level": 100}) model.notification.create_repo_notification(complex_repo, "vulnerability_found", "quay_notification", {}, {"level": 100}) # Ensure that there are no event queue items for the layer. self.assertIsNone(notification_queue.get()) with fake_security_scanner() as security_scanner: # Test with an unknown notification. worker = SecurityNotificationWorker(None) self.assertFalse( worker.perform_notification_work( {"Name": "unknownnotification"})) # Add some analyzed layers. analyzer = LayerAnalyzer(app.config, self.api) analyzer.analyze_recursively(layer1) analyzer.analyze_recursively(layer2) # Add a notification with pages of data. new_vuln_info = { "Name": "CVE-TEST", "Namespace": "debian:8", "Description": "Some service", "Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471", "Severity": "Critical", "FixedIn": { "Version": "9.23-5" }, } security_scanner.set_vulns(security_scanner.layer_id(layer1), [new_vuln_info]) security_scanner.set_vulns(security_scanner.layer_id(layer2), [new_vuln_info]) # Define offsetting sets of layer IDs, to test cross-pagination support. In this test, we # will only serve 2 layer IDs per page: the first page will serve both of the 'New' layer IDs, # but since the first 2 'Old' layer IDs are "earlier" than the shared ID of # `devtable/simple:latest`, they won't get served in the 'New' list until the *second* page. # The notification handling system should correctly not notify for this layer, even though it # is marked 'New' on page 1 and marked 'Old' on page 2. Clair will served these # IDs sorted in the same manner. idx_old_layer_ids = [ { "LayerName": "old1", "Index": 1 }, { "LayerName": "old2", "Index": 2 }, { "LayerName": security_scanner.layer_id(layer1), "Index": 3 }, ] idx_new_layer_ids = [ { "LayerName": security_scanner.layer_id(layer1), "Index": 3 }, { "LayerName": security_scanner.layer_id(layer2), "Index": 4 }, ] old_layer_ids = [t["LayerName"] for t in idx_old_layer_ids] new_layer_ids = [t["LayerName"] for t in idx_new_layer_ids] if not indexed: idx_old_layer_ids = None idx_new_layer_ids = None notification_data = security_scanner.add_notification( old_layer_ids, new_layer_ids, None, new_vuln_info, max_per_page=2, indexed_old_layer_ids=idx_old_layer_ids, indexed_new_layer_ids=idx_new_layer_ids, ) # Test with a known notification with pages. data = { "Name": notification_data["Name"], } worker = SecurityNotificationWorker(None) self.assertTrue( worker.perform_notification_work(data, layer_limit=2)) # Make sure all pages were processed by ensuring we have only one notification. If the second # page was not processed, then the `Old` entry for layer1 will not be found, and we'd get two # notifications. time.sleep(1) self.assertIsNotNone(notification_queue.get()) self.assertIsNone(notification_queue.get())
def test_notification_no_new_layers_increased_severity(self): layer = _get_legacy_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest", include_storage=True) layer_id = "%s.%s" % (layer.docker_image_id, layer.storage.uuid) # Add a repo event for the layer. repo = model.repository.get_repository(ADMIN_ACCESS_USER, SIMPLE_REPO) notification = model.notification.create_repo_notification( repo, "vulnerability_found", "quay_notification", {}, {"level": 100}) # Ensure that there are no event queue items for the layer. self.assertIsNone(notification_queue.get()) # Fire off the notification processing. with fake_security_scanner() as security_scanner: analyzer = LayerAnalyzer(app.config, self.api) analyzer.analyze_recursively(layer) layer = _get_legacy_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest") self.assertAnalyzed(layer, security_scanner, True, 1) old_vuln_info = { "Name": "CVE-TEST", "Namespace": "debian:8", "Description": "Some service", "Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471", "Severity": "Low", } new_vuln_info = { "Name": "CVE-TEST", "Namespace": "debian:8", "Description": "Some service", "Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471", "Severity": "Critical", "FixedIn": { "Version": "9.23-5" }, } security_scanner.set_vulns(layer_id, [new_vuln_info]) # Add a notification for the layer. notification_data = security_scanner.add_notification( [layer_id], [layer_id], old_vuln_info, new_vuln_info) # Process the notification. self.assertTrue( process_notification_data(self.api, notification_data)) # Ensure an event was written for the tag. time.sleep(1) queue_item = notification_queue.get() self.assertIsNotNone(queue_item) item_body = json.loads(queue_item.body) self.assertEquals(sorted(["prod", "latest"]), sorted(item_body["event_data"]["tags"])) self.assertEquals("CVE-TEST", item_body["event_data"]["vulnerability"]["id"]) self.assertEquals( "Critical", item_body["event_data"]["vulnerability"]["priority"]) self.assertTrue( item_body["event_data"]["vulnerability"]["has_fix"]) # Verify that an event would be raised. event_data = item_body["event_data"] notification = self.notification_tuple(notification) self.assertTrue(VulnerabilityFoundEvent().should_perform( event_data, notification)) # Create another notification with a matching level and verify it will be raised. notification = model.notification.create_repo_notification( repo, "vulnerability_found", "quay_notification", {}, {"level": 1}) notification = self.notification_tuple(notification) self.assertTrue(VulnerabilityFoundEvent().should_perform( event_data, notification)) # Create another notification with a higher level and verify it won't be raised. notification = model.notification.create_repo_notification( repo, "vulnerability_found", "quay_notification", {}, {"level": 0}) notification = self.notification_tuple(notification) self.assertFalse(VulnerabilityFoundEvent().should_perform( event_data, notification))
def test_notification_worker(self): layer1 = _get_legacy_image(ADMIN_ACCESS_USER, SIMPLE_REPO, "latest", include_storage=True) layer2 = _get_legacy_image(ADMIN_ACCESS_USER, COMPLEX_REPO, "prod", include_storage=True) # Add a repo events for the layers. simple_repo = model.repository.get_repository(ADMIN_ACCESS_USER, SIMPLE_REPO) complex_repo = model.repository.get_repository(ADMIN_ACCESS_USER, COMPLEX_REPO) model.notification.create_repo_notification(simple_repo, "vulnerability_found", "quay_notification", {}, {"level": 100}) model.notification.create_repo_notification(complex_repo, "vulnerability_found", "quay_notification", {}, {"level": 100}) # Ensure that there are no event queue items for the layer. self.assertIsNone(notification_queue.get()) with fake_security_scanner() as security_scanner: # Test with an unknown notification. worker = SecurityNotificationWorker(None) self.assertFalse( worker.perform_notification_work( {"Name": "unknownnotification"})) # Add some analyzed layers. analyzer = LayerAnalyzer(app.config, self.api) analyzer.analyze_recursively(layer1) analyzer.analyze_recursively(layer2) # Add a notification with pages of data. new_vuln_info = { "Name": "CVE-TEST", "Namespace": "debian:8", "Description": "Some service", "Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471", "Severity": "Critical", "FixedIn": { "Version": "9.23-5" }, } security_scanner.set_vulns(security_scanner.layer_id(layer1), [new_vuln_info]) security_scanner.set_vulns(security_scanner.layer_id(layer2), [new_vuln_info]) layer_ids = [ security_scanner.layer_id(layer1), security_scanner.layer_id(layer2) ] notification_data = security_scanner.add_notification( [], layer_ids, None, new_vuln_info) # Test with a known notification with pages. data = { "Name": notification_data["Name"], } worker = SecurityNotificationWorker(None) self.assertTrue( worker.perform_notification_work(data, layer_limit=2)) # Make sure all pages were processed by ensuring we have two notifications. time.sleep(1) self.assertIsNotNone(notification_queue.get()) self.assertIsNotNone(notification_queue.get())
def test_notification_new_layers(self): layer = model.tag.get_tag_image(ADMIN_ACCESS_USER, SIMPLE_REPO, 'latest', include_storage=True) layer_id = '%s.%s' % (layer.docker_image_id, layer.storage.uuid) # Add a repo event for the layer. repo = model.repository.get_repository(ADMIN_ACCESS_USER, SIMPLE_REPO) model.notification.create_repo_notification(repo, 'vulnerability_found', 'quay_notification', {}, {'level': 100}) # Ensure that there are no event queue items for the layer. self.assertIsNone(notification_queue.get()) # Fire off the notification processing. with fake_security_scanner() as security_scanner: analyzer = LayerAnalyzer(app.config, self.api) analyzer.analyze_recursively(layer) layer = model.tag.get_tag_image(ADMIN_ACCESS_USER, SIMPLE_REPO, 'latest') self.assertAnalyzed(layer, security_scanner, True, 1) vuln_info = { "Name": "CVE-TEST", "Namespace": "debian:8", "Description": "Some service", "Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471", "Severity": "Low", "FixedIn": { "Version": "9.23-5" }, } security_scanner.set_vulns(layer_id, [vuln_info]) # Add a notification for the layer. notification_data = security_scanner.add_notification([], [layer_id], vuln_info, vuln_info) # Process the notification. self.assertTrue(process_notification_data(notification_data)) # Ensure an event was written for the tag. time.sleep(1) queue_item = notification_queue.get() self.assertIsNotNone(queue_item) item_body = json.loads(queue_item.body) self.assertEquals(sorted(['prod', 'latest']), sorted(item_body['event_data']['tags'])) self.assertEquals('CVE-TEST', item_body['event_data']['vulnerability']['id']) self.assertEquals( 'Low', item_body['event_data']['vulnerability']['priority']) self.assertTrue( item_body['event_data']['vulnerability']['has_fix'])