diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/__init__.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.metadata.json b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.metadata.json new file mode 100644 index 0000000000..b9d7964461 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "networkfirewall_multi_az", + "CheckTitle": "Ensure all Network Firewall Firewalls are deployed across multiple AZ.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "network-firewall", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:network-firewall::account-id:firewall/firewall-name", + "Severity": "medium", + "ResourceType": "AwsNetworkFirewallFirewall", + "Description": "Ensure all Network Firewall Firewalls are deployed in at least 2 Availability Zones.", + "Risk": "Deploying a Network Firewall in a single Availability Zone increases the risk of service disruption if that AZ experiences an outage, potentially leaving your network vulnerable to attacks or downtime.", + "RelatedUrl": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/arch-two-zone-igw.html", + "Remediation": { + "Code": { + "CLI": "aws network-firewall update-firewall-delete-protection --firewall-arn --firewall-name --subnet-mappings SubnetId=,SubnetId=", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/networkfirewall-controls.html#networkfirewall-1", + "Terraform": "" + }, + "Recommendation": { + "Text": "Deploy your AWS Network Firewall across multiple Availability Zones to enhance availability, ensure traffic load balancing, and minimize the impact of any AZ-specific failures.", + "Url": "https://aws.amazon.com/es/blogs/networking-and-content-delivery/deployment-models-for-aws-network-firewall/" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.py new file mode 100644 index 0000000000..da31c55b35 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.networkfirewall.networkfirewall_client import ( + networkfirewall_client, +) + + +class networkfirewall_multi_az(Check): + def execute(self): + findings = [] + for firewall in networkfirewall_client.network_firewalls.values(): + report = Check_Report_AWS(self.metadata()) + report.region = firewall.region + report.resource_id = firewall.name + report.resource_arn = firewall.arn + report.resource_tags = firewall.tags + report.status = "FAIL" + report.status_extended = ( + f"Network Firewall {firewall.name} is not deployed across multiple AZ." + ) + + if len(firewall.subnet_mappings) > 1: + report.status = "PASS" + report.status_extended = ( + f"Network Firewall {firewall.name} is deployed across multiple AZ." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py index a12b19cc85..97413a8033 100644 --- a/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py @@ -64,6 +64,16 @@ class NetworkFirewall(AWSService): network_firewall.deletion_protection = describe_firewall.get( "DeleteProtection", False ) + for subnet in describe_firewall.get("SubnetMappings", []): + if subnet.get("SubnetId"): + network_firewall.subnet_mappings.append( + Subnet( + subnet_id=subnet.get("SubnetId"), + ip_addr_type=subnet.get( + "IPAddressType", IPAddressType.IPV4 + ), + ) + ) except Exception as error: logger.error( f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" @@ -148,6 +158,21 @@ class LoggingConfiguration(BaseModel): log_destination: dict = {} +class IPAddressType(Enum): + """Enum for IP Address Type""" + + IPV4 = "IPV4" + IPV6 = "IPV6" + DUALSTACK = "DUALSTACK" + + +class Subnet(BaseModel): + """Subnet model for SubnetMappings""" + + subnet_id: str + ip_addr_type: IPAddressType + + class Firewall(BaseModel): """Firewall Model for Network Firewall""" @@ -159,6 +184,7 @@ class Firewall(BaseModel): tags: list = [] encryption_type: str = None deletion_protection: bool = False + subnet_mappings: list[Subnet] = [] logging_configuration: Optional[list[LoggingConfiguration]] stateless_rule_groups: list[str] = [] stateful_rule_groups: list[str] = [] diff --git a/tests/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az_test.py b/tests/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az_test.py new file mode 100644 index 0000000000..6e0d5f6750 --- /dev/null +++ b/tests/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az_test.py @@ -0,0 +1,155 @@ +from unittest import mock + +from prowler.providers.aws.services.networkfirewall.networkfirewall_service import ( + Firewall, + IPAddressType, + Subnet, +) +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +FIREWALL_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall/my-firewall" +FIREWALL_NAME = "my-firewall" +VPC_ID_PROTECTED = "vpc-12345678901234567" +VPC_ID_UNPROTECTED = "vpc-12345678901234568" +POLICY_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall-policy/my-policy" + + +class Test_networkfirewall_multi_az: + def test_no_networkfirewall(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = {} + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az import ( + networkfirewall_multi_az, + ) + + check = networkfirewall_multi_az() + result = check.execute() + + assert len(result) == 0 + + def test_networkfirewall_multi_az_disabled(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=False, + subnet_mappings=[ + Subnet( + subnet_id="subnet-12345678901234567", + ip_addr_type=IPAddressType.IPV4, + ) + ], + ) + } + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az import ( + networkfirewall_multi_az, + ) + + check = networkfirewall_multi_az() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} is not deployed across multiple AZ." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_multi_az_enabled(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=True, + subnet_mappings=[ + Subnet( + subnet_id="subnet-12345678901234567", + ip_addr_type=IPAddressType.IPV4, + ), + Subnet( + subnet_id="subnet-12345678901234568", + ip_addr_type=IPAddressType.IPV4, + ), + ], + ) + } + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az import ( + networkfirewall_multi_az, + ) + + check = networkfirewall_multi_az() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} is deployed across multiple AZ." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN