feat(providers/huaweicloud): add functiongraph_function_vpc_configured check

This commit is contained in:
tomitobio committed 2026-07-29 04:11:52 +08:00
1 parent 1218b0920f
commit ec6878c824
7 files changed
+328

No files matched your search

Whitespace-only changes.
@@ -0,0 +1,6 @@
from prowler.providers.common.provider import Provider
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_service import (
FunctionGraph,
)
functiongraph_client = FunctionGraph(Provider.get_global_provider())
@@ -0,0 +1,34 @@
{
"Provider": "huaweicloud",
"CheckID": "functiongraph_function_vpc_configured",
"CheckTitle": "FunctionGraph functions are configured within a VPC",
"CheckType": [],
"ServiceName": "functiongraph",
"SubServiceName": "",
"ResourceIdTemplate": "function/{function_id}",
"Severity": "medium",
"ResourceType": "FunctionGraphFunction",
"ResourceGroup": "serverless",
"Description": "Ensure FunctionGraph functions are associated with a VPC to restrict network access",
"Risk": "Functions not associated with a VPC have direct internet access without network restrictions, increasing the attack surface",
"RelatedUrl": "",
"AdditionalURLs": [],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Associate the FunctionGraph function with a VPC to restrict network access",
"Url": "https://hub.prowler.com/check/functiongraph_function_vpc_configured"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,30 @@
from prowler.lib.check.models import Check, CheckReportHuaweiCloud
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_client import (
functiongraph_client,
)
class functiongraph_function_vpc_configured(Check):
"""Check if FunctionGraph functions are configured within a VPC."""
def execute(self) -> list[CheckReportHuaweiCloud]:
findings = []
for function in functiongraph_client.functions:
report = CheckReportHuaweiCloud(
metadata=self.metadata(),
resource=function,
)
report.region = function.region
report.resource_id = function.function_id
report.resource_arn = f"huaweicloud:functiongraph:{function.region}:{functiongraph_client.audited_account}:function/{function.function_id}"
if function.func_vpc_id:
report.status = "PASS"
report.status_extended = f"Function '{function.name}' is configured within VPC '{function.func_vpc_id}'."
else:
report.status = "FAIL"
report.status_extended = f"Function '{function.name}' is not associated with a VPC and has direct internet access without network restrictions."
findings.append(report)
return findings
@@ -0,0 +1,95 @@
from typing import List, Optional
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
from prowler.providers.huaweicloud.lib.service.service import HuaweiCloudService
class FunctionGraph(HuaweiCloudService):
"""
FunctionGraph service class for Huawei Cloud.
This class provides methods to interact with Huawei Cloud FunctionGraph service
to retrieve serverless functions and their security configuration.
"""
def __init__(self, provider):
super().__init__(__class__.__name__, provider)
self.functions: List[FunctionGraphFunction] = []
if self.session.is_mock:
self._load_mock_data()
return
self._list_functions()
def _load_mock_data(self):
"""Load mock data for testing."""
region = "la-south-2"
self.functions = [
FunctionGraphFunction(
function_id="fg-mock-001",
name="function-secure",
runtime="Python3.9",
timeout=30,
memory_size=128,
func_vpc_id="vpc-12345",
region=region,
),
FunctionGraphFunction(
function_id="fg-mock-002",
name="function-insecure",
runtime="Python3.9",
timeout=30,
memory_size=128,
func_vpc_id=None,
region=region,
),
]
def _list_functions(self):
"""List all FunctionGraph functions across regions."""
if not self.regional_clients:
return
for region, client in self.regional_clients.items():
logger.info(f"FunctionGraph - Listing Functions in {region}...")
try:
from huaweicloudsdkfunctiongraph.v2 import ListFunctionsRequest
request = ListFunctionsRequest()
response = self._call_with_retries(client.list_functions, request)
if response and response.functions:
for func in response.functions:
self.functions.append(
FunctionGraphFunction(
function_id=getattr(func, "resource_id", ""),
name=getattr(func, "func_name", ""),
runtime=getattr(func, "runtime", ""),
timeout=getattr(func, "timeout", 0),
memory_size=getattr(func, "memory_size", 0),
func_vpc_id=getattr(func, "func_vpc_id", None),
region=region,
)
)
except Exception as error:
logger.error(
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class FunctionGraphFunction(BaseModel):
"""FunctionGraph function model."""
function_id: str
name: str = ""
runtime: str = ""
timeout: int = 0
memory_size: int = 0
func_vpc_id: Optional[str] = None
region: str = ""
@@ -0,0 +1,163 @@
from unittest import mock
from tests.providers.huaweicloud.huaweicloud_fixtures import (
set_mocked_huaweicloud_provider,
)
class Test_functiongraph_function_vpc_configured:
def test_functiongraph_vpc_configured_pass(self):
functiongraph_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_huaweicloud_provider(),
),
mock.patch(
"prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured.functiongraph_client",
new=functiongraph_client,
),
):
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured import (
functiongraph_function_vpc_configured,
)
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_service import (
FunctionGraphFunction,
)
functiongraph_client.functions = [
FunctionGraphFunction(
function_id="fg-001",
name="function-secure",
runtime="Python3.9",
timeout=30,
memory_size=128,
func_vpc_id="vpc-12345",
region="la-south-2",
),
]
functiongraph_client.audited_account = "123456789012"
check = functiongraph_function_vpc_configured()
results = check.execute()
assert len(results) == 1
assert results[0].status == "PASS"
assert results[0].resource_id == "fg-001"
assert "configured within VPC" in results[0].status_extended
def test_functiongraph_vpc_configured_fail(self):
functiongraph_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_huaweicloud_provider(),
),
mock.patch(
"prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured.functiongraph_client",
new=functiongraph_client,
),
):
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured import (
functiongraph_function_vpc_configured,
)
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_service import (
FunctionGraphFunction,
)
functiongraph_client.functions = [
FunctionGraphFunction(
function_id="fg-002",
name="function-insecure",
runtime="Python3.9",
timeout=30,
memory_size=128,
func_vpc_id=None,
region="la-south-2",
),
]
functiongraph_client.audited_account = "123456789012"
check = functiongraph_function_vpc_configured()
results = check.execute()
assert len(results) == 1
assert results[0].status == "FAIL"
assert results[0].resource_id == "fg-002"
assert "not associated with a VPC" in results[0].status_extended
def test_functiongraph_vpc_configured_mixed(self):
functiongraph_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_huaweicloud_provider(),
),
mock.patch(
"prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured.functiongraph_client",
new=functiongraph_client,
),
):
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured import (
functiongraph_function_vpc_configured,
)
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_service import (
FunctionGraphFunction,
)
functiongraph_client.functions = [
FunctionGraphFunction(
function_id="fg-001",
name="function-secure",
runtime="Python3.9",
timeout=30,
memory_size=128,
func_vpc_id="vpc-12345",
region="la-south-2",
),
FunctionGraphFunction(
function_id="fg-002",
name="function-insecure",
runtime="Python3.9",
timeout=30,
memory_size=128,
func_vpc_id=None,
region="la-south-2",
),
]
functiongraph_client.audited_account = "123456789012"
check = functiongraph_function_vpc_configured()
results = check.execute()
assert len(results) == 2
assert results[0].status == "PASS"
assert results[1].status == "FAIL"
def test_functiongraph_vpc_configured_empty(self):
functiongraph_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_huaweicloud_provider(),
),
mock.patch(
"prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured.functiongraph_client",
new=functiongraph_client,
),
):
from prowler.providers.huaweicloud.services.functiongraph.functiongraph_function_vpc_configured.functiongraph_function_vpc_configured import (
functiongraph_function_vpc_configured,
)
functiongraph_client.functions = []
functiongraph_client.audited_account = "123456789012"
check = functiongraph_function_vpc_configured()
results = check.execute()
assert len(results) == 0