From 17a04a57604d311861a23ef8a0f44de212761c4e Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Wed, 13 Nov 2024 16:10:15 +0100 Subject: [PATCH] fix(gcp): scan only ACTIVE projects (#5752) Co-authored-by: Sergio Garcia --- .../providers/gcp/exceptions/exceptions.py | 4 +- prowler/providers/gcp/gcp_provider.py | 32 ++++--- tests/providers/gcp/gcp_provider_test.py | 84 ++++++++++++++++--- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/prowler/providers/gcp/exceptions/exceptions.py b/prowler/providers/gcp/exceptions/exceptions.py index c61bc525f8e..2232d692f9a 100644 --- a/prowler/providers/gcp/exceptions/exceptions.py +++ b/prowler/providers/gcp/exceptions/exceptions.py @@ -15,8 +15,8 @@ class GCPBaseException(ProwlerException): "remediation": "Check the HTTP error and ensure the request is properly formatted.", }, (3002, "GCPNoAccesibleProjectsError"): { - "message": "No Project IDs can be accessed via Google Credentials", - "remediation": "Ensure the project is accessible and properly set up.", + "message": "No Project IDs are active or can be accessed via Google Credentials", + "remediation": "Ensure the project is active and accessible.", }, (3003, "GCPSetUpSessionError"): { "message": "Error setting up session", diff --git a/prowler/providers/gcp/gcp_provider.py b/prowler/providers/gcp/gcp_provider.py index 72529a563c1..6ad8e90f315 100644 --- a/prowler/providers/gcp/gcp_provider.py +++ b/prowler/providers/gcp/gcp_provider.py @@ -105,22 +105,28 @@ def __init__( file=__file__, message="No Project IDs can be accessed via Google Credentials.", ) - if project_ids: + if self._default_project_id not in project_ids: + self._default_project_id = project_ids[0] for input_project in project_ids: - for accessible_project in accessible_projects: - if self.is_project_matching(input_project, accessible_project): - self._projects[accessible_project] = accessible_projects[ - accessible_project - ] - self._project_ids.append( - accessible_projects[accessible_project].id - ) + for ( + accessible_project_id, + accessible_project, + ) in accessible_projects.items(): + # Only scan active projects + if accessible_project.lifecycle_state == "ACTIVE": + if self.is_project_matching( + input_project, accessible_project_id + ): + self._projects[accessible_project_id] = accessible_project + self._project_ids.append(accessible_project_id) else: # If not projects were input, all accessible projects are scanned by default for project_id, project in accessible_projects.items(): - self._projects[project_id] = project - self._project_ids.append(project_id) + # Only scan active projects + if project.lifecycle_state == "ACTIVE": + self._projects[project_id] = project + self._project_ids.append(project_id) # Remove excluded projects if any input if excluded_project_ids: @@ -134,11 +140,11 @@ def __init__( if not self._projects: logger.critical( - "No Input Project IDs can be accessed via Google Credentials." + "No Input Project IDs are active or can be accessed via Google Credentials." ) raise GCPNoAccesibleProjectsError( file=__file__, - message="No Input Project IDs can be accessed via Google Credentials.", + message="No Input Project IDs are active or can be accessed via Google Credentials.", ) if list_project_ids: diff --git a/tests/providers/gcp/gcp_provider_test.py b/tests/providers/gcp/gcp_provider_test.py index d32fa47b284..9c748219358 100644 --- a/tests/providers/gcp/gcp_provider_test.py +++ b/tests/providers/gcp/gcp_provider_test.py @@ -14,6 +14,7 @@ from prowler.providers.common.models import Connection from prowler.providers.gcp.exceptions.exceptions import ( GCPInvalidProviderIdError, + GCPNoAccesibleProjectsError, GCPTestConnectionError, ) from prowler.providers.gcp.gcp_provider import GcpProvider @@ -40,7 +41,7 @@ def test_gcp_provider(self): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ) } @@ -109,7 +110,7 @@ def test_is_project_matching(self): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ) } @@ -183,7 +184,7 @@ def test_setup_session_with_credentials_file_no_impersonate(self): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ) } @@ -247,7 +248,7 @@ def test_setup_session_with_credentials_file_and_impersonate(self): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ) } @@ -319,7 +320,7 @@ def test_setup_session_with_organization_id(self): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", organization=GCPOrganization( id="test-organization-id", name="test-organization", @@ -369,6 +370,69 @@ def test_setup_session_with_organization_id(self): == "test-organization-id" ) + def test_setup_session_with_inactive_project(self): + mocked_credentials = MagicMock() + + mocked_credentials.refresh.return_value = None + mocked_credentials._service_account_email = "test-service-account-email" + + arguments = Namespace() + arguments.project_id = ["project/55555555"] + 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 + + projects = { + "test-project": GCPProject( + number="55555555", + id="project/55555555", + name="test-project", + labels={"test": "value"}, + lifecycle_state="DELETE_REQUESTED", + ) + } + + mocked_service = MagicMock() + + mocked_service.projects.list.return_value = MagicMock( + execute=MagicMock(return_value={"projects": projects}) + ) + 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, + ): + with pytest.raises(Exception) as e: + GcpProvider( + arguments.organization_id, + arguments.project_id, + arguments.excluded_project_id, + arguments.credentials_file, + arguments.impersonate_service_account, + arguments.list_project_id, + arguments.config_file, + arguments.fixer_config, + client_id=None, + client_secret=None, + refresh_token=None, + ) + assert e.type == GCPNoAccesibleProjectsError + def test_print_credentials_default_options(self, capsys): mocked_credentials = MagicMock() @@ -391,7 +455,7 @@ def test_print_credentials_default_options(self, capsys): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ) } @@ -462,7 +526,7 @@ def test_print_credentials_impersonated_service_account(self, capsys): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ) } @@ -533,14 +597,14 @@ def test_print_credentials_excluded_project_ids(self, capsys): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ), "test-excluded-project": GCPProject( number="12345678", id="project/12345678", name="test-excluded-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ), } @@ -658,7 +722,7 @@ def test_test_connection_invalid_project_id(self): id="project/55555555", name="test-project", labels={"test": "value"}, - lifecycle_state="", + lifecycle_state="ACTIVE", ), }