def test_load_security_information_api_request_failure(initialized_db, set_secscan_config): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest", include_legacy_image=True) manifest = registry_model.get_manifest_for_tag(tag, backfill_if_necessary=True) mss = ManifestSecurityStatus.create( manifest=manifest._db_id, repository=repository_ref._db_id, error_json={}, index_status=IndexStatus.COMPLETED, indexer_hash="abc", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.vulnerability_report.side_effect = APIRequestFailure() assert secscan.load_security_information( manifest).status == ScanLookupStatus.COULD_NOT_LOAD assert not ManifestSecurityStatus.select().where( ManifestSecurityStatus.id == mss.id).exists()
def test_load_security_information_api_returns_none(initialized_db, set_secscan_config): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest", include_legacy_image=True) manifest = registry_model.get_manifest_for_tag(tag, backfill_if_necessary=True) ManifestSecurityStatus.create( manifest=manifest._db_id, repository=repository_ref._db_id, error_json={}, index_status=IndexStatus.COMPLETED, indexer_hash="abc", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.vulnerability_report.return_value = None assert secscan.load_security_information( manifest).status == ScanLookupStatus.NOT_YET_INDEXED
def test_process_notification_page(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) results = list( secscan.process_notification_page([ { "reason": "removed", }, { "reason": "added", "manifest": "sha256:abcd", "vulnerability": { "normalized_severity": "s", "description": "d", "package": { "id": "42", "name": "p", "version": "v0.0.1", }, "name": "n", "fixed_in_version": "f", "links": "l", }, }, ])) assert len(results) == 1 assert results[0].manifest_digest == "sha256:abcd" assert results[0].vulnerability.Severity == "s" assert results[0].vulnerability.Description == "d" assert results[0].vulnerability.NamespaceName == "p" assert results[0].vulnerability.Name == "n" assert results[0].vulnerability.FixedBy == "f" assert results[0].vulnerability.Link == "l"
def test_perform_indexing_failed_within_reindex_threshold( initialized_db, set_secscan_config): app.config["SECURITY_SCANNER_V4_REINDEX_THRESHOLD"] = 300 expected_manifests = (Manifest.select().join(Repository).join(User).where( User.username == "devtable")) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "abc"} secscan._secscan_api.index.return_value = ( { "err": None, "state": IndexReportState.Index_Finished }, "abc", ) for manifest in expected_manifests: ManifestSecurityStatus.create( manifest=manifest, repository=manifest.repository, error_json={}, index_status=IndexStatus.FAILED, indexer_hash="abc", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan.perform_indexing() assert ManifestSecurityStatus.select().count() == expected_manifests.count( ) for mss in ManifestSecurityStatus.select(): assert mss.index_status == IndexStatus.FAILED
def test_perform_indexing_api_request_failure_index(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "abc"} secscan._secscan_api.index.side_effect = APIRequestFailure() next_token = secscan.perform_indexing() assert next_token is None assert ManifestSecurityStatus.select().count() == 0 # Set security scanner to return good results and attempt indexing again secscan._secscan_api.index.side_effect = None secscan._secscan_api.index.return_value = ( { "err": None, "state": IndexReportState.Index_Finished }, "abc", ) next_token = secscan.perform_indexing() assert next_token.min_id == Manifest.select(fn.Max( Manifest.id)).scalar() + 1 assert ManifestSecurityStatus.select().count() == Manifest.select( fn.Max(Manifest.id)).count()
def test_perform_indexing_failed(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "abc"} secscan._secscan_api.index.return_value = ( { "err": None, "state": IndexReportState.Index_Finished }, "abc", ) for manifest in Manifest.select(): ManifestSecurityStatus.create( manifest=manifest, repository=manifest.repository, error_json={}, index_status=IndexStatus.FAILED, indexer_hash="abc", indexer_version=IndexerVersion.V4, last_indexed=datetime.utcnow() - timedelta( seconds=app.config["SECURITY_SCANNER_V4_REINDEX_THRESHOLD"] + 60), metadata_json={}, ) secscan.perform_indexing() assert ManifestSecurityStatus.select().count() == Manifest.select().count() for mss in ManifestSecurityStatus.select(): assert mss.index_status == IndexStatus.COMPLETED
def test_perform_indexing_whitelist(initialized_db, set_secscan_config): app.config["SECURITY_SCANNER_V4_NAMESPACE_WHITELIST"] = ["devtable"] expected_manifests = (Manifest.select().join(Repository).join(User).where( User.username == "devtable")) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "abc"} secscan._secscan_api.index.return_value = ( { "err": None, "state": IndexReportState.Index_Finished }, "abc", ) next_token = secscan.perform_indexing() assert secscan._secscan_api.index.call_count == expected_manifests.count() for mss in ManifestSecurityStatus.select(): assert mss.repository.namespace_user.username == "devtable" assert ManifestSecurityStatus.select().count() == expected_manifests.count( ) assert ( Manifest.get_by_id(next_token.min_id - 1).repository.namespace_user.username == "devtable")
def test_perform_indexing_api_request_failure_index(initialized_db): app.config["SECURITY_SCANNER_V4_NAMESPACE_WHITELIST"] = ["devtable"] expected_manifests = ( Manifest.select(fn.Max(Manifest.id)) .join(Repository) .join(User) .where(User.username == "devtable") ) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = "abc" secscan._secscan_api.index.side_effect = APIRequestFailure() next_token = secscan.perform_indexing() assert next_token is None assert ManifestSecurityStatus.select().count() == 0 # Set security scanner to return good results and attempt indexing again secscan._secscan_api.index.side_effect = None secscan._secscan_api.index.return_value = ( {"err": None, "state": IndexReportState.Index_Finished}, "abc", ) next_token = secscan.perform_indexing() assert next_token.min_id == expected_manifests.scalar() + 1 assert ManifestSecurityStatus.select().count() == expected_manifests.count()
def test_perform_indexing_needs_reindexing_skip_unsupported( initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "new hash"} secscan._secscan_api.index.return_value = ( { "err": None, "state": IndexReportState.Index_Finished }, "new hash", ) for manifest in Manifest.select(): ManifestSecurityStatus.create( manifest=manifest, repository=manifest.repository, error_json={}, index_status=IndexStatus.MANIFEST_UNSUPPORTED, indexer_hash="old hash", indexer_version=IndexerVersion.V4, last_indexed=datetime.utcnow() - timedelta( seconds=app.config["SECURITY_SCANNER_V4_REINDEX_THRESHOLD"] + 60), metadata_json={}, ) secscan.perform_indexing() # Since this manifest should not be scanned, the old hash should remain assert ManifestSecurityStatus.select().count() == Manifest.select().count() for mss in ManifestSecurityStatus.select(): assert mss.indexer_hash == "old hash"
def test_load_security_information_queued(initialized_db): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest", include_legacy_image=True) manifest = registry_model.get_manifest_for_tag(tag, backfill_if_necessary=True) secscan = V4SecurityScanner(app, instance_keys, storage) assert secscan.load_security_information(manifest).status == ScanLookupStatus.NOT_YET_INDEXED
def test_manifest_iterator(initialized_db, set_secscan_config, index_status, indexer_state, seconds, expect_zero): secscan = V4SecurityScanner(app, instance_keys, storage) for manifest in Manifest.select(): with db_transaction(): ManifestSecurityStatus.delete().where( ManifestSecurityStatus.manifest == manifest, ManifestSecurityStatus.repository == manifest.repository, ).execute() ManifestSecurityStatus.create( manifest=manifest, repository=manifest.repository, error_json={}, index_status=index_status, indexer_hash="old hash", indexer_version=IndexerVersion.V4, last_indexed=datetime.utcnow() - timedelta(seconds=seconds), metadata_json={}, ) iterator = secscan._get_manifest_iterator( indexer_state, Manifest.select(fn.Min(Manifest.id)).scalar(), Manifest.select(fn.Max(Manifest.id)).scalar(), ) count = 0 for candidate, abt, num_remaining in iterator: count = count + 1 if expect_zero: assert count == 0 else: assert count != 0
def test_perform_indexing_needs_reindexing(initialized_db): app.config["SECURITY_SCANNER_V4_NAMESPACE_WHITELIST"] = ["devtable"] expected_manifests = ( Manifest.select().join(Repository).join(User).where(User.username == "devtable") ) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = "xyz" secscan._secscan_api.index.return_value = ( {"err": None, "state": IndexReportState.Index_Finished}, "xyz", ) for manifest in expected_manifests: ManifestSecurityStatus.create( manifest=manifest, repository=manifest.repository, error_json={}, index_status=IndexStatus.COMPLETED, indexer_hash="abc", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan.perform_indexing() assert ManifestSecurityStatus.select().count() == expected_manifests.count() for mss in ManifestSecurityStatus.select(): assert mss.indexer_hash == "xyz"
def test_perform_indexing_needs_reindexing_within_reindex_threshold( initialized_db, set_secscan_config): app.config["SECURITY_SCANNER_V4_REINDEX_THRESHOLD"] = 300 secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "xyz"} secscan._secscan_api.index.return_value = ( { "err": None, "state": IndexReportState.Index_Finished }, "xyz", ) for manifest in Manifest.select(): ManifestSecurityStatus.create( manifest=manifest, repository=manifest.repository, error_json={}, index_status=IndexStatus.COMPLETED, indexer_hash="abc", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan.perform_indexing() assert ManifestSecurityStatus.select().count() == Manifest.select().count() for mss in ManifestSecurityStatus.select(): assert mss.indexer_hash == "abc"
def test_load_security_information_queued(initialized_db, set_secscan_config): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest") manifest = registry_model.get_manifest_for_tag(tag) secscan = V4SecurityScanner(app, instance_keys, storage) assert secscan.load_security_information(manifest).status == ScanLookupStatus.NOT_YET_INDEXED
def test_load_security_information_success(initialized_db, set_secscan_config): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest") manifest = registry_model.get_manifest_for_tag(tag) ManifestSecurityStatus.create( manifest=manifest._db_id, repository=repository_ref._db_id, error_json={}, index_status=IndexStatus.COMPLETED, indexer_hash="abc", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.vulnerability_report.return_value = { "manifest_hash": manifest.digest, "state": "IndexFinished", "packages": {}, "distributions": {}, "repository": {}, "environments": {}, "package_vulnerabilities": {}, "success": True, "err": "", } result = secscan.load_security_information(manifest) assert result.status == ScanLookupStatus.SUCCESS assert result.security_information == SecurityInformation( Layer(manifest.digest, "", "", 4, []))
def test_lookup_notification_page_invalid(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.retrieve_notification_page.return_value = None result = secscan.lookup_notification_page("someinvalidid") assert result.status == PaginatedNotificationStatus.FATAL_ERROR
def test_perform_indexing_api_request_failure_state(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.side_effect = APIRequestFailure() next_token = secscan.perform_indexing() assert next_token is None assert ManifestSecurityStatus.select().count() == 0
def test_perform_indexing_whitelist(initialized_db): app.config["SECURITY_SCANNER_V4_NAMESPACE_WHITELIST"] = ["devtable"] secscan = V4SecurityScanner(app, instance_keys, storage) next_token = secscan.perform_indexing() assert ( Manifest.get_by_id(next_token.min_id - 1).repository.namespace_user.username == "devtable")
def test_perform_indexing_api_request_failure_state(initialized_db): app.config["SECURITY_SCANNER_V4_NAMESPACE_WHITELIST"] = ["devtable"] secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.side_effect = APIRequestFailure() next_token = secscan.perform_indexing() assert next_token is None assert ManifestSecurityStatus.select().count() == 0
def test_perform_indexing_invalid_manifest(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() # Delete all ManifestBlob rows to cause the manifests to be invalid. ManifestBlob.delete().execute() secscan.perform_indexing() assert ManifestSecurityStatus.select().count() == Manifest.select().count() for mss in ManifestSecurityStatus.select(): assert mss.index_status == IndexStatus.MANIFEST_UNSUPPORTED
def test_perform_indexing_api_request_non_finished_state(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "xyz"} secscan._secscan_api.index.return_value = ( {"err": "something", "state": "ScanLayers"}, "xyz", ) next_token = secscan.perform_indexing() assert next_token and next_token.min_id == Manifest.select(fn.Max(Manifest.id)).scalar() + 1 assert ManifestSecurityStatus.select().count() == 0
def test_perform_indexing_api_request_index_error_response(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "xyz"} secscan._secscan_api.index.return_value = ( {"err": "something", "state": IndexReportState.Index_Error}, "xyz", ) next_token = secscan.perform_indexing() assert next_token.min_id == Manifest.select(fn.Max(Manifest.id)).scalar() + 1 assert ManifestSecurityStatus.select().count() == Manifest.select(fn.Max(Manifest.id)).count() for mss in ManifestSecurityStatus.select(): assert mss.index_status == IndexStatus.FAILED
def configure(self, app, instance_keys, storage): # TODO(alecmerdler): Just use `V4SecurityScanner` once Clair V2 is removed. self._model = V2SecurityScanner(app, instance_keys, storage) self._v4_model = V4SecurityScanner(app, instance_keys, storage) self._v4_namespace_whitelist = app.config.get( "SECURITY_SCANNER_V4_NAMESPACE_WHITELIST", []) logger.info("===============================") logger.info( "Using split secscan model: v4 whitelist `%s`", self._v4_namespace_whitelist, ) logger.info("===============================") return self
def test_perform_indexing_manifest_list(initialized_db, set_secscan_config): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest") manifest = registry_model.get_manifest_for_tag(tag) Manifest.update(media_type=MediaType.get( name=DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE)).execute() secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan.perform_indexing() assert ManifestSecurityStatus.select().count() == Manifest.select().count() for mss in ManifestSecurityStatus.select(): assert mss.index_status == IndexStatus.MANIFEST_UNSUPPORTED
def test_perform_indexing_empty_whitelist(initialized_db): app.config["SECURITY_SCANNER_V4_NAMESPACE_WHITELIST"] = [] secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = "abc" secscan._secscan_api.index.return_value = ( {"err": None, "state": IndexReportState.Index_Finished}, "abc", ) next_token = secscan.perform_indexing() assert secscan._secscan_api.index.call_count == 0 assert ManifestSecurityStatus.select().count() == 0 assert next_token.min_id == Manifest.select(fn.Max(Manifest.id)).scalar() + 1
def configure(self, app, instance_keys, storage): try: self._model = V4SecurityScanner(app, instance_keys, storage) except InvalidConfigurationException: self._model = NoopV4SecurityScanner() try: self._legacy_model = V2SecurityScanner(app, instance_keys, storage) except InvalidConfigurationException: self._legacy_model = NoopV2SecurityScanner() logger.info("===============================") logger.info("Using split secscan model: `%s`", [self._legacy_model, self._model]) logger.info("===============================") return self
def test_perform_indexing_whitelist(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.state.return_value = {"state": "abc"} secscan._secscan_api.index.return_value = ( {"err": None, "state": IndexReportState.Index_Finished}, "abc", ) next_token = secscan.perform_indexing() assert next_token.min_id == Manifest.select(fn.Max(Manifest.id)).scalar() + 1 assert secscan._secscan_api.index.call_count == Manifest.select().count() assert ManifestSecurityStatus.select().count() == Manifest.select().count() for mss in ManifestSecurityStatus.select(): assert mss.index_status == IndexStatus.COMPLETED
def test_load_security_information_failed_to_index(initialized_db): repository_ref = registry_model.lookup_repository("devtable", "simple") tag = registry_model.get_repo_tag(repository_ref, "latest", include_legacy_image=True) manifest = registry_model.get_manifest_for_tag(tag, backfill_if_necessary=True) ManifestSecurityStatus.create( manifest=manifest._db_id, repository=repository_ref._db_id, error_json='failed to fetch layers: encountered error while fetching a layer: fetcher: unknown content-type "binary/octet-stream"', index_status=IndexStatus.FAILED, indexer_hash="", indexer_version=IndexerVersion.V4, metadata_json={}, ) secscan = V4SecurityScanner(app, instance_keys, storage) assert secscan.load_security_information(manifest).status == ScanLookupStatus.FAILED_TO_INDEX
def test_lookup_notification_page_valid(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.retrieve_notification_page.return_value = { "notifications": [{ "id": "5e4b387e-88d3-4364-86fd-063447a6fad2", "manifest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", "reason": "added", "vulnerability": {}, }], "page": {}, } result = secscan.lookup_notification_page( "5e4b387e-88d3-4364-86fd-063447a6fad2") assert result.status == PaginatedNotificationStatus.SUCCESS assert result.next_page_index is None assert ( result.data[0]["manifest"] == "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a" )
def test_mark_notification_handled(initialized_db, set_secscan_config): secscan = V4SecurityScanner(app, instance_keys, storage) secscan._secscan_api = mock.Mock() secscan._secscan_api.delete_notification.return_value = True assert secscan.mark_notification_handled("somevalidid") == True