def test_lock_timeout(self, setup_for_thread_testing):
        """
        Verifies that error is raised when the lock timeout is met by a thread
        Ensures this happening by creating a thread that runs longer than timeout
        Runs task while lock is taken and assert that error is raised
        Patches timeout to 1 second
        """
        with ThreadPoolExecutor() as executor:
            # run thread1
            thread1 = executor.submit(GrypeDBSyncManager.run_grypedb_sync,
                                      Mock(), "test/bypass/catalog.txt")

            # Wait until thread1 has taken the lock and then run thread2 with timeout of ~5 seconds
            lock_acquired = False
            for attempt in range(5):
                if GrypeDBSyncLock._lock.locked():
                    lock_acquired = True
                    with pytest.raises(GrypeDBSyncLockAquisitionTimeout):
                        GrypeDBSyncManager.run_grypedb_sync(
                            Mock(),
                            grypedb_file_path="test/bypass/catalog.txt")
                    break
                else:
                    time.sleep(1)

            assert thread1.result() is True
            assert lock_acquired is True
    def test_no_active_grypedb(self, monkeypatch):
        def _mocked_call(session):
            raise NoActiveGrypeDB

        monkeypatch.setattr(
            "anchore_engine.services.policy_engine.engine.feeds.grypedb_sync.get_most_recent_active_grypedb",
            _mocked_call,
        )

        with pytest.raises(NoActiveDBSyncError):
            GrypeDBSyncManager.run_grypedb_sync(Mock())
Esempio n. 3
0
 def execute(self):
     """
     Runs the GrypeDBSyncTask by calling the GrypeDBSyncManager.
     """
     with session_scope() as session:
         try:
             GrypeDBSyncManager.run_grypedb_sync(session,
                                                 self.grypedb_file_path)
         except NoActiveDBSyncError:
             logger.warn(
                 "Cannot initialize grype db locally since no record was found"
             )
    def test_lock_across_threads(self, setup_for_thread_testing):
        """
        Verifies the output of the tasks when designed to ensure that one thread hits the lock before the other finishes

        Runs 2 tasks: creates thread that runs sync task and then another task is run synchronously (synchronous_task)
        Mocks the update_grypedb function to wait 5 seconds to ensure race condition with thread1 and synchronous_task
        The mock also updates active and local grype dbs to mimic real behavior
        Run thread1 and once the lock is taken, it runs synchronous_task, which is identical to thread1
        If lock correctly blocks synchronous_task from evaluating, only thread1 should run the update_grypedb method
        """

        with ThreadPoolExecutor() as executor:
            # run thread1
            thread1 = executor.submit(GrypeDBSyncManager.run_grypedb_sync,
                                      "test/bypass/catalog.txt")

            # Wait until thread1 has taken the lock and then run thread2 with timeout of ~5 seconds
            synchronous_task = False
            lock_acquired = False
            for attempt in range(10):
                if GrypeDBSyncLock._lock.locked():
                    lock_acquired = True
                    synchronous_task = GrypeDBSyncManager.run_grypedb_sync(
                        Mock(), grypedb_file_path="test/bypass/catalog.txt")
                    break
                else:
                    time.sleep(1)

            assert thread1.result() is True
            assert lock_acquired is True
            assert synchronous_task is False
    def test_matching_checksums(self, mock_calls_for_sync):
        checksum = "eef3b1bcd5728346cb1b30eae09647348bacfbde3ba225d70cb0374da249277c"
        mock_calls_for_sync(
            mock_active_db=GrypeDBFeedMetadata(archive_checksum=checksum),
            mock_local_checksum=checksum,
        )

        sync_ran = GrypeDBSyncManager.run_grypedb_sync(Mock())

        assert sync_ran is False
    def test_mismatch_checksum(self, mock_calls_for_sync, monkeypatch):
        global_checksum = (
            "eef3b1bcd5728346cb1b30eae09647348bacfbde3ba225d70cb0374da249277c")
        local_checksum = (
            "366ab0a94f4ed9c22f5cc93e4d8f6724163a357ae5190740c1b5f251fd706cc4")

        mock_calls_for_sync(
            mock_active_db=GrypeDBFeedMetadata(
                archive_checksum=global_checksum),
            mock_local_checksum=local_checksum,
        )

        # mock execution of update
        monkeypatch.setattr(GrypeDBSyncManager, "_update_grypedb",
                            Mock(return_value=True))

        # pass a file path to bypass connection to catalog to retrieve tar from object storage
        sync_ran = GrypeDBSyncManager.run_grypedb_sync(
            Mock(), grypedb_file_path="test/bypass/catalog.txt")

        assert sync_ran is True
    def test_class_lock_called(self, mock_calls_for_sync, monkeypatch):
        """
        Verfies that the lock enter and exit methods are called to ensure that the lock is being used correctly
        Verifies on matching checksum in order to assert the lock is called even when the task is not executed
        """
        checksum = "366ab0a94f4ed9c22f5cc93e4d8f6724163a357ae5190740c1b5f251fd706cc4"
        mock_lock = MagicMock()
        monkeypatch.setattr(GrypeDBSyncLock, "_lock", mock_lock)
        mock_calls_for_sync(
            mock_active_db=GrypeDBFeedMetadata(archive_checksum=checksum),
            mock_local_checksum="",
        )

        monkeypatch.setattr(GrypeDBSyncManager, "_update_grypedb",
                            Mock(return_value=True))

        sync_ran = GrypeDBSyncManager.run_grypedb_sync(
            Mock(), grypedb_file_path="test/bypass/catalog.txt")

        assert sync_ran is True
        assert mock_lock.acquire.called is True
        assert mock_lock.release.called is True
 def test_uninitialized_grype_wrapper(self):
     assert GrypeDBSyncManager._get_local_grypedb_checksum() is None
Esempio n. 9
0
    def scan_image_for_vulnerabilities(
        self, image: Image, db_session
    ) -> ImageVulnerabilitiesReport:
        logger.info(
            "Scanning image %s/%s for vulnerabilities",
            image.user_id,
            image.id,
        )

        report = ImageVulnerabilitiesReport(
            account_id=image.user_id,
            image_id=image.id,
            results=[],
            metadata=VulnerabilitiesReportMetadata(
                schema_version="1.0",
                generated_at=datetime.datetime.utcnow(),
                generated_by={"scanner": self.__class__.__name__},
            ),
            problems=[],
        )

        # check and run grype sync if necessary
        try:
            GrypeDBSyncManager.run_grypedb_sync(db_session)
        except NoActiveDBSyncError:
            logger.exception("Failed to initialize local vulnerability database")
            report.problems.append(
                VulnerabilityScanProblem(
                    details="No vulnerability database found in the system. Retry after a feed sync completes setting up the vulnerability database"
                )
            )
            return report

        # create the image sbom
        try:
            sbom = to_grype_sbom(
                image, image.packages, self._get_image_cpes(image, db_session)
            )
        except Exception:
            logger.exception(
                "Failed to create the image sbom for %s/%s", image.user_id, image.id
            )
            report.problems.append(
                VulnerabilityScanProblem(details="Failed to create the image sbom")
            )
            return report

        # submit the sbom to grype wrapper and get results
        try:
            if SAVE_SBOM_TO_FILE:
                # don't bail on errors writing to file since this is for debugging only
                try:
                    file_path = "{}/sbom_{}.json".format(
                        localconfig.get_config().get("tmp_dir", "/tmp"), image.id
                    )
                    logger.debug("Writing image sbom for %s to %s", image.id, file_path)

                    with open(file_path, "w") as fp:
                        json.dump(sbom, fp, indent=2)
                except Exception:
                    logger.exception(
                        "Ignoring error writing the image sbom to file for  %s/%s Moving on",
                        image.user_id,
                        image.id,
                    )

            # submit the image for analysis to grype
            grype_response = (
                GrypeWrapperSingleton.get_instance().get_vulnerabilities_for_sbom(
                    json.dumps(sbom)
                )
            )
        except Exception:
            logger.exception(
                "Failed to scan image sbom for vulnerabilities using grype for %s/%s",
                image.user_id,
                image.id,
            )
            report.problems.append(
                VulnerabilityScanProblem(
                    details="Failed to scan image sbom for vulnerabilities using grype"
                )
            )
            return report

        # transform grype response to engine vulnerabilities and dedup
        try:
            results = to_engine_vulnerabilities(grype_response)
            report.results = get_image_vulnerabilities_deduper().execute(results)
            report.metadata.generated_by = self._get_report_generated_by(grype_response)
        except Exception:
            logger.exception("Failed to transform grype vulnerabilities response")
            report.problems.append(
                VulnerabilityScanProblem(
                    details="Failed to transform grype vulnerabilities response"
                )
            )
            return report

        return report