From 24364bd73e9db890efcbfa3953afa552922d15d5 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 27 Aug 2025 13:44:34 +0100 Subject: [PATCH] feat(gcp): Add support for skipping APIs check (#8575) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 2 +- prowler/providers/common/provider.py | 1 + prowler/providers/gcp/gcp_provider.py | 6 ++ .../providers/gcp/lib/arguments/arguments.py | 7 ++ prowler/providers/gcp/lib/service/service.py | 5 +- tests/providers/gcp/gcp_provider_test.py | 87 +++++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 9ced1b7941..7323a56ad2 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -15,7 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `eks_cluster_deletion_protection_enabled` check for AWS provider [(#8536)](https://github.com/prowler-cloud/prowler/pull/8536) - ECS privilege escalation patterns (StartTask and RunTask) for AWS provider [(#8541)](https://github.com/prowler-cloud/prowler/pull/8541) - Resource Explorer enumeration v2 API actions in `cloudtrail_threat_detection_enumeration` check [(#8557)](https://github.com/prowler-cloud/prowler/pull/8557) - +- GCP `--skip-api-check` command line flag [(#8575)](https://github.com/prowler-cloud/prowler/pull/8575) ### Changed - Refine kisa isms-p compliance mapping [(#8479)](https://github.com/prowler-cloud/prowler/pull/8479) diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index 1b2de71148..c09c5113d9 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -202,6 +202,7 @@ class Provider(ABC): config_path=arguments.config_file, mutelist_path=arguments.mutelist_file, fixer_config=fixer_config, + skip_api_check=arguments.skip_api_check, ) elif "kubernetes" in provider_class_name.lower(): provider_class( diff --git a/prowler/providers/gcp/gcp_provider.py b/prowler/providers/gcp/gcp_provider.py index 7cdbba4401..c9e7c25501 100644 --- a/prowler/providers/gcp/gcp_provider.py +++ b/prowler/providers/gcp/gcp_provider.py @@ -86,6 +86,7 @@ class GcpProvider(Provider): client_secret: str = None, refresh_token: str = None, service_account_key: dict = None, + skip_api_check: bool = False, ): """ GCP Provider constructor @@ -107,6 +108,7 @@ class GcpProvider(Provider): client_secret: str refresh_token: str service_account_key: dict + skip_api_check: bool Raises: GCPNoAccesibleProjectsError if no project IDs can be accessed via Google Credentials @@ -174,6 +176,10 @@ class GcpProvider(Provider): gcp_config.DEFAULT_RETRY_ATTEMPTS = retries_max_attempts logger.info(f"GCP retry attempts set to {retries_max_attempts}") + if skip_api_check: + logger.info("Skipping API active check for each service") + self.skip_api_check = skip_api_check + self._impersonated_service_account = impersonate_service_account # Set the GCP credentials using the provided client_id, client_secret and refresh_token gcp_credentials = None diff --git a/prowler/providers/gcp/lib/arguments/arguments.py b/prowler/providers/gcp/lib/arguments/arguments.py index 6866898caa..8172630132 100644 --- a/prowler/providers/gcp/lib/arguments/arguments.py +++ b/prowler/providers/gcp/lib/arguments/arguments.py @@ -57,3 +57,10 @@ def init_parser(self): type=int, help="Set the maximum attempts for the Google Cloud SDK retry config (Default: 3)", ) + + gcp_config_subparser.add_argument( + "--skip-api-check", + action="store_true", + default=False, + help="Assume all APIs are active and skip the active API check for each service", + ) diff --git a/prowler/providers/gcp/lib/service/service.py b/prowler/providers/gcp/lib/service/service.py index f32b29c2af..746952542f 100644 --- a/prowler/providers/gcp/lib/service/service.py +++ b/prowler/providers/gcp/lib/service/service.py @@ -29,7 +29,10 @@ class GCPService: self.service, api_version, self.credentials ) # Only project ids that have their API enabled will be scanned - self.project_ids = self.__is_api_active__(provider.project_ids) + if provider.skip_api_check: + self.project_ids = provider.project_ids + else: + self.project_ids = self.__is_api_active__(provider.project_ids) self.projects = provider.projects self.default_project_id = provider.default_project_id self.audit_config = provider.audit_config diff --git a/tests/providers/gcp/gcp_provider_test.py b/tests/providers/gcp/gcp_provider_test.py index e2196dddbf..63741ed647 100644 --- a/tests/providers/gcp/gcp_provider_test.py +++ b/tests/providers/gcp/gcp_provider_test.py @@ -987,3 +987,90 @@ class TestGCPProvider: from prowler.providers.gcp.config import DEFAULT_RETRY_ATTEMPTS assert DEFAULT_RETRY_ATTEMPTS == 3 + + def test_skip_api_check_argument(self): + """Test that skip_api_check argument is set correctly in GcpProvider""" + + mocked_credentials = MagicMock() + + mocked_credentials.refresh.return_value = None + mocked_credentials._service_account_email = "test-service-account-email" + + arguments = Namespace() + arguments.project_id = [] + arguments.excluded_project_id = [] + arguments.organization_id = None + arguments.list_project_id = False + arguments.credentials_file = "test_credentials_file" + arguments.impersonate_service_account = "" + arguments.config_file = default_config_file_path + arguments.fixer_config = default_fixer_config_file_path + arguments.skip_api_check = True + + projects = { + "test-project": GCPProject( + number="55555555", + id="project/55555555", + name="test-project", + labels={"test": "value"}, + lifecycle_state="ACTIVE", + ), + } + + mocked_service = MagicMock() + + mocked_service.projects.list.return_value = MagicMock( + execute=MagicMock(return_value={"projects": projects}) + ) + + mocked_is_api_active = MagicMock() + mocked_is_api_active.return_value = projects.keys() + + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), + patch( + "prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__", + mocked_is_api_active, + ), + ): + gcp_provider = GcpProvider( + retries_max_attempts=None, + organization_id=arguments.organization_id, + project_ids=arguments.project_id, + excluded_project_ids=arguments.excluded_project_id, + credentials_file=arguments.credentials_file, + impersonate_service_account=arguments.impersonate_service_account, + list_project_ids=arguments.list_project_id, + config_path=arguments.config_file, + fixer_config=arguments.fixer_config, + client_id=None, + client_secret=None, + refresh_token=None, + skip_api_check=arguments.skip_api_check, + ) + + from prowler.providers.gcp.lib.service.service import GCPService + + GCPService("testservice", gcp_provider) + + assert gcp_provider.skip_api_check is True + mocked_is_api_active.assert_not_called()