from unittest.mock import MagicMock, patch from prowler.providers.gcp.services.cloudfunction.cloudfunction_service import ( CloudFunction, ) from tests.providers.gcp.gcp_fixtures import ( GCP_PROJECT_ID, mock_is_api_active, set_mocked_gcp_provider, ) _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, iam_bindings=None): """Return a mock GCP API client for the Cloud Functions v2 service.""" client = MagicMock() client.projects().locations().list().execute.return_value = { "locations": [{"locationId": _LOCATION_ID}] } client.projects().locations().list_next.return_value = None client.projects().locations().functions().list().execute.return_value = { "functions": 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 class TestCloudFunctionService: def test_get_functions_with_vpc_connector(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, "vpcConnector": _CONNECTOR, }, } ] ) 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.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): return _make_cloudfunction_client( functions_list=[ { "name": f"projects/{GCP_PROJECT_ID}/locations/{_LOCATION_ID}/functions/no-vpc-func", "state": "ACTIVE", "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__", new=mock_is_api_active, ), patch( "prowler.providers.gcp.lib.service.service.GCPService.__generate_client__", new=mock_api_client, ), ): cf_client = CloudFunction( set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID]) ) assert len(cf_client.functions) == 1 assert cf_client.functions[0].environment == "GEN_1" assert cf_client.functions[0].publicly_accessible is True