Skip to content

Commit

Permalink
fix(gcp): scan only ACTIVE projects (#5752)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
  • Loading branch information
prowler-bot and MrCloudSec authored Nov 13, 2024
1 parent 786e1f8 commit 17a04a5
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 25 deletions.
4 changes: 2 additions & 2 deletions prowler/providers/gcp/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
32 changes: 19 additions & 13 deletions prowler/providers/gcp/gcp_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
84 changes: 74 additions & 10 deletions tests/providers/gcp/gcp_provider_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,7 +41,7 @@ def test_gcp_provider(self):
id="project/55555555",
name="test-project",
labels={"test": "value"},
lifecycle_state="",
lifecycle_state="ACTIVE",
)
}

Expand Down Expand Up @@ -109,7 +110,7 @@ def test_is_project_matching(self):
id="project/55555555",
name="test-project",
labels={"test": "value"},
lifecycle_state="",
lifecycle_state="ACTIVE",
)
}

Expand Down Expand Up @@ -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",
)
}

Expand Down Expand Up @@ -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",
)
}

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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()

Expand All @@ -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",
)
}

Expand Down Expand Up @@ -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",
)
}

Expand Down Expand Up @@ -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",
),
}

Expand Down Expand Up @@ -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",
),
}

Expand Down

0 comments on commit 17a04a5

Please sign in to comment.