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