mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-30 23:34:27 +00:00
feat(gcp): add cloudfunction_function_not_publicly_accessible check (#11022)
Co-authored-by: Lydia Vilchez <lydiavilchezlopez@gmail.com>
This commit is contained in:
1 parent
a7917f779a
commit
ccc1f161d2
10 files changed
+602
-7
No files matched your search
@@ -11,12 +11,18 @@ from tests.providers.gcp.gcp_fixtures import (
|
||||
|
||||
_LOCATION_ID = "us-central1"
|
||||
_FUNCTION_NAME = "my-function"
|
||||
_FUNCTION_ID = (
|
||||
f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/functions/{_FUNCTION_NAME}"
|
||||
)
|
||||
_RUN_SERVICE = (
|
||||
f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/services/{_FUNCTION_NAME}"
|
||||
)
|
||||
_CONNECTOR = (
|
||||
f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/connectors/my-connector"
|
||||
)
|
||||
|
||||
|
||||
def _make_cloudfunction_client(functions_list):
|
||||
def _make_cloudfunction_client(functions_list, iam_bindings=None):
|
||||
"""Return a mock GCP API client for the Cloud Functions v2 service."""
|
||||
client = MagicMock()
|
||||
|
||||
@@ -30,6 +36,29 @@ def _make_cloudfunction_client(functions_list):
|
||||
}
|
||||
client.projects().locations().functions().list_next.return_value = None
|
||||
|
||||
iam_response = {"bindings": iam_bindings or []}
|
||||
|
||||
def mock_get_iam_policy(resource):
|
||||
rv = MagicMock()
|
||||
rv.execute.return_value = iam_response
|
||||
return rv
|
||||
|
||||
client.projects().locations().functions().getIamPolicy = mock_get_iam_policy
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def _make_run_client(iam_bindings=None):
|
||||
"""Return a mock Cloud Run v2 client for gen2 IAM policy lookups."""
|
||||
client = MagicMock()
|
||||
iam_response = {"bindings": iam_bindings or []}
|
||||
|
||||
def mock_get_iam_policy(resource):
|
||||
rv = MagicMock()
|
||||
rv.execute.return_value = iam_response
|
||||
return rv
|
||||
|
||||
client.projects().locations().services().getIamPolicy = mock_get_iam_policy
|
||||
return client
|
||||
|
||||
|
||||
@@ -39,9 +68,11 @@ class TestCloudFunctionService:
|
||||
return _make_cloudfunction_client(
|
||||
functions_list=[
|
||||
{
|
||||
"name": f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/functions/{_FUNCTION_NAME}",
|
||||
"name": _FUNCTION_ID,
|
||||
"state": "ACTIVE",
|
||||
"environment": "GEN_2",
|
||||
"serviceConfig": {
|
||||
"service": _RUN_SERVICE,
|
||||
"vpcConnector": _CONNECTOR,
|
||||
},
|
||||
}
|
||||
@@ -57,6 +88,10 @@ class TestCloudFunctionService:
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=mock_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.cloudfunction.cloudfunction_service.discovery.build",
|
||||
return_value=_make_run_client(),
|
||||
),
|
||||
):
|
||||
cf_client = CloudFunction(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
@@ -64,11 +99,15 @@ class TestCloudFunctionService:
|
||||
|
||||
assert len(cf_client.functions) == 1
|
||||
fn = cf_client.functions[0]
|
||||
assert fn.id == _FUNCTION_ID
|
||||
assert fn.name == _FUNCTION_NAME
|
||||
assert fn.project_id == GCP_PROJECT_ID
|
||||
assert fn.location == _LOCATION_ID
|
||||
assert fn.state == "ACTIVE"
|
||||
assert fn.environment == "GEN_2"
|
||||
assert fn.service == _RUN_SERVICE
|
||||
assert fn.vpc_connector == _CONNECTOR
|
||||
assert fn.publicly_accessible is False
|
||||
|
||||
def test_get_functions_without_vpc_connector(self):
|
||||
def mock_api_client(*args, **kwargs):
|
||||
@@ -77,11 +116,190 @@ class TestCloudFunctionService:
|
||||
{
|
||||
"name": f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/functions/no-vpc-func",
|
||||
"state": "ACTIVE",
|
||||
"serviceConfig": {},
|
||||
"environment": "GEN_2",
|
||||
"serviceConfig": {
|
||||
"service": f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/services/no-vpc-func",
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=mock_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.cloudfunction.cloudfunction_service.discovery.build",
|
||||
return_value=_make_run_client(),
|
||||
),
|
||||
):
|
||||
cf_client = CloudFunction(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
|
||||
assert len(cf_client.functions) == 1
|
||||
fn = cf_client.functions[0]
|
||||
assert fn.name == "no-vpc-func"
|
||||
assert fn.vpc_connector is None
|
||||
assert fn.publicly_accessible is False
|
||||
|
||||
def test_get_functions_iam_policy_gen2_all_users(self):
|
||||
"""Gen2 functions: allUsers binding lives on the Cloud Run service."""
|
||||
|
||||
def mock_api_client(*args, **kwargs):
|
||||
return _make_cloudfunction_client(
|
||||
functions_list=[
|
||||
{
|
||||
"name": _FUNCTION_ID,
|
||||
"state": "ACTIVE",
|
||||
"environment": "GEN_2",
|
||||
"serviceConfig": {"service": _RUN_SERVICE},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
run_client = _make_run_client(
|
||||
iam_bindings=[
|
||||
{
|
||||
"role": "roles/run.invoker",
|
||||
"members": ["allUsers"],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=mock_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.cloudfunction.cloudfunction_service.discovery.build",
|
||||
return_value=run_client,
|
||||
),
|
||||
):
|
||||
cf_client = CloudFunction(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
|
||||
assert len(cf_client.functions) == 1
|
||||
assert cf_client.functions[0].publicly_accessible is True
|
||||
|
||||
def test_get_functions_iam_policy_gen2_all_authenticated_users(self):
|
||||
def mock_api_client(*args, **kwargs):
|
||||
return _make_cloudfunction_client(
|
||||
functions_list=[
|
||||
{
|
||||
"name": _FUNCTION_ID,
|
||||
"state": "ACTIVE",
|
||||
"environment": "GEN_2",
|
||||
"serviceConfig": {"service": _RUN_SERVICE},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
run_client = _make_run_client(
|
||||
iam_bindings=[
|
||||
{
|
||||
"role": "roles/run.invoker",
|
||||
"members": ["allAuthenticatedUsers"],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=mock_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.cloudfunction.cloudfunction_service.discovery.build",
|
||||
return_value=run_client,
|
||||
),
|
||||
):
|
||||
cf_client = CloudFunction(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
|
||||
assert len(cf_client.functions) == 1
|
||||
assert cf_client.functions[0].publicly_accessible is True
|
||||
|
||||
def test_get_functions_iam_policy_gen2_not_public(self):
|
||||
def mock_api_client(*args, **kwargs):
|
||||
return _make_cloudfunction_client(
|
||||
functions_list=[
|
||||
{
|
||||
"name": _FUNCTION_ID,
|
||||
"state": "ACTIVE",
|
||||
"environment": "GEN_2",
|
||||
"serviceConfig": {"service": _RUN_SERVICE},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
run_client = _make_run_client(
|
||||
iam_bindings=[
|
||||
{
|
||||
"role": "roles/run.invoker",
|
||||
"members": ["serviceAccount:sa@project.iam.gserviceaccount.com"],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=mock_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.cloudfunction.cloudfunction_service.discovery.build",
|
||||
return_value=run_client,
|
||||
),
|
||||
):
|
||||
cf_client = CloudFunction(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
|
||||
assert len(cf_client.functions) == 1
|
||||
assert cf_client.functions[0].publicly_accessible is False
|
||||
|
||||
def test_get_functions_iam_policy_gen1_all_users(self):
|
||||
"""Gen1 functions: IAM binding lives on the Cloud Functions resource itself."""
|
||||
|
||||
def mock_api_client(*args, **kwargs):
|
||||
return _make_cloudfunction_client(
|
||||
functions_list=[
|
||||
{
|
||||
"name": _FUNCTION_ID,
|
||||
"state": "ACTIVE",
|
||||
"environment": "GEN_1",
|
||||
"serviceConfig": {},
|
||||
}
|
||||
],
|
||||
iam_bindings=[
|
||||
{
|
||||
"role": "roles/cloudfunctions.invoker",
|
||||
"members": ["allUsers"],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
@@ -97,6 +315,5 @@ class TestCloudFunctionService:
|
||||
)
|
||||
|
||||
assert len(cf_client.functions) == 1
|
||||
fn = cf_client.functions[0]
|
||||
assert fn.name == "no-vpc-func"
|
||||
assert fn.vpc_connector is None
|
||||
assert cf_client.functions[0].environment == "GEN_1"
|
||||
assert cf_client.functions[0].publicly_accessible is True
|
||||
Reference in new issue
Block a user