Ejemplo n.º 1
0
    def test_notification_no_new_layers(self):
        layer = model.tag.get_tag_image(ADMIN_ACCESS_USER,
                                        SIMPLE_REPO,
                                        "latest",
                                        include_storage=True)

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

            # Add a notification for the layer.
            notification_data = security_scanner.add_notification([], [], {},
                                                                  {})

            # Process the notification.
            self.assertTrue(process_notification_data(notification_data))

            # Ensure that there are no event queue items for the layer.
            self.assertIsNone(notification_queue.get())
Ejemplo n.º 2
0
    def test_notification_new_layers_not_vulnerable(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)

            # Add a notification for the layer.
            notification_data = security_scanner.add_notification([layer_id],
                                                                  [], {}, {})

            # Process the notification.
            self.assertTrue(process_notification_data(notification_data))

            # Ensure that there are no event queue items for the layer.
            self.assertIsNone(notification_queue.get())
Ejemplo n.º 3
0
    def assert_notification_worker_offset_pages(self, indexed=False):
        layer1 = model.tag.get_tag_image(ADMIN_ACCESS_USER,
                                         SIMPLE_REPO,
                                         "latest",
                                         include_storage=True)
        layer2 = model.tag.get_tag_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())
Ejemplo n.º 4
0
    def test_notification_worker(self):
        layer1 = model.tag.get_tag_image(ADMIN_ACCESS_USER,
                                         SIMPLE_REPO,
                                         "latest",
                                         include_storage=True)
        layer2 = model.tag.get_tag_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())
Ejemplo n.º 5
0
    def test_notification_no_new_layers_increased_severity(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)
        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 = model.tag.get_tag_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(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))
Ejemplo n.º 6
0
    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 test_notification(issue, initialized_db):
    worker = SecurityScanningNotificationWorker(secscan_notification_queue)
    secscan_model.configure(app, instance_keys, storage)
    worker._secscan_model = secscan_model

    hostname = urlparse(app.config["SECURITY_SCANNER_V4_ENDPOINT"]).netloc
    with fake_security_scanner(hostname=hostname) as fake:
        repository_ref = registry_model.lookup_repository("devtable", "simple")

        # Add a security notification event to the repository.
        if issue != "no_event_registered":
            model.notification.create_repo_notification(
                repository_ref.id,
                "vulnerability_found",
                "webhook",
                {},
                {
                    "vulnerability": {
                        "priority": "Low" if issue != "severity_too_low" else "Critical",
                    },
                },
            )

        tag = registry_model.get_repo_tag(repository_ref, "latest")
        manifest = registry_model.get_manifest_for_tag(tag)

        # Add a notification to the scanner, matching the manifest.
        notification_id = "somenotificationid"
        fake.add_notification(
            notification_id if issue != "wrong_id" else "wrongid",
            manifest.digest if issue != "no_matching_manifest" else "sha256:incorrect",
            "added",
            {
                "normalized_severity": "High",
                "description": "Some description",
                "package": {
                    "id": "42",
                    "name": "FooBar",
                    "version": "v0.0.1",
                },
                "name": "BarBaz",
                "links": "http://example.com",
            },
        )

        # Add the notification to the queue.
        name = ["with_id", notification_id]
        secscan_notification_queue.put(
            name,
            json.dumps({"notification_id": notification_id}),
        )

        # Process the notification via the worker.
        worker.poll_queue()

        # Ensure the repository notification was enqueued.
        found = notification_queue.get()
        if issue:
            assert found is None
            return

        assert found is not None

        body = json.loads(found["body"])

        assert body["event_data"]["repository"] == "devtable/simple"
        assert body["event_data"]["namespace"] == "devtable"
        assert body["event_data"]["name"] == "simple"
        assert body["event_data"]["tags"] == ["latest"]
        assert body["event_data"]["vulnerability"]["id"] == "BarBaz"
        assert body["event_data"]["vulnerability"]["description"] == "Some description"
        assert body["event_data"]["vulnerability"]["priority"] == "High"
Ejemplo n.º 8
0
    def test_notification_new_layers(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)
        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)

            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(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(
                "Low", item_body["event_data"]["vulnerability"]["priority"])
            self.assertTrue(
                item_body["event_data"]["vulnerability"]["has_fix"])