From b9298b4023f14cf5e119468c7e19914b5cd5a97b Mon Sep 17 00:00:00 2001 From: s1ns3nz0 Date: Mon, 22 Jun 2026 23:34:52 +0900 Subject: [PATCH] feat(azure): add network_vnet_ddos_protection_enabled check (#11044) Co-authored-by: Daniel Barranquero --- prowler/CHANGELOG.md | 1 + prowler/compliance/azure/cis_2.0_azure.json | 3 +- prowler/compliance/azure/cis_5.0_azure.json | 4 +- .../compliance/azure/iso27001_2022_azure.json | 1 + .../azure/services/network/network_service.py | 4 + .../__init__.py | 0 ...vnet_ddos_protection_enabled.metadata.json | 36 +++++++ .../network_vnet_ddos_protection_enabled.py | 41 ++++++++ ...twork_vnet_ddos_protection_enabled_test.py | 99 +++++++++++++++++++ 9 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/__init__.py create mode 100644 prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.metadata.json create mode 100644 prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.py create mode 100644 tests/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index b479000569..7dd9d94d3f 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -29,6 +29,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `aks_cluster_azure_monitor_enabled` check for Azure provider, verifying AKS clusters have Azure Monitor (Container Insights) enabled for metrics, logs, and alerting [(#11029)](https://github.com/prowler-cloud/prowler/pull/11029) - `aks_cluster_local_accounts_disabled` check for Azure provider, verifying AKS clusters have local accounts disabled so authentication is forced through Microsoft Entra ID [(#11030)](https://github.com/prowler-cloud/prowler/pull/11030) - `network_subnet_nsg_associated` check for Azure provider, verifying virtual network subnets have a network security group associated to enforce traffic filtering [(#11043)](https://github.com/prowler-cloud/prowler/pull/11043) +- `network_vnet_ddos_protection_enabled` check for Azure provider, verifying virtual networks have Azure DDoS Network Protection enabled [(#11044)](https://github.com/prowler-cloud/prowler/pull/11044) - `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027) - Public `Provider.get_class()` method that resolves a provider class by name for both built-in and external (entry-point) providers [(#11398)](https://github.com/prowler-cloud/prowler/pull/11398) - Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602) diff --git a/prowler/compliance/azure/cis_2.0_azure.json b/prowler/compliance/azure/cis_2.0_azure.json index 73f4dc3611..59bd153352 100644 --- a/prowler/compliance/azure/cis_2.0_azure.json +++ b/prowler/compliance/azure/cis_2.0_azure.json @@ -2714,7 +2714,8 @@ "Id": "6.6", "Description": "Ensure that Network Watcher is 'Enabled'", "Checks": [ - "network_watcher_enabled" + "network_watcher_enabled", + "network_vnet_ddos_protection_enabled" ], "Attributes": [ { diff --git a/prowler/compliance/azure/cis_5.0_azure.json b/prowler/compliance/azure/cis_5.0_azure.json index 35f77ce310..8ea6f50d7c 100644 --- a/prowler/compliance/azure/cis_5.0_azure.json +++ b/prowler/compliance/azure/cis_5.0_azure.json @@ -2935,7 +2935,9 @@ { "Id": "8.5", "Description": "Ensure Azure DDoS Network Protection is enabled on virtual networks", - "Checks": [], + "Checks": [ + "network_vnet_ddos_protection_enabled" + ], "Attributes": [ { "Section": "8 Security Services", diff --git a/prowler/compliance/azure/iso27001_2022_azure.json b/prowler/compliance/azure/iso27001_2022_azure.json index 6659e6ee0a..c1d92b1dc5 100644 --- a/prowler/compliance/azure/iso27001_2022_azure.json +++ b/prowler/compliance/azure/iso27001_2022_azure.json @@ -1431,6 +1431,7 @@ "network_ssh_internet_access_restricted", "network_subnet_nsg_associated", "network_udp_internet_access_restricted", + "network_vnet_ddos_protection_enabled", "network_watcher_enabled" ] }, diff --git a/prowler/providers/azure/services/network/network_service.py b/prowler/providers/azure/services/network/network_service.py index 98668bdbfa..a924cf9609 100644 --- a/prowler/providers/azure/services/network/network_service.py +++ b/prowler/providers/azure/services/network/network_service.py @@ -227,6 +227,9 @@ class Network(AzureService): id=vnet.id, name=vnet.name, location=vnet.location, + enable_ddos_protection=getattr( + vnet, "enable_ddos_protection", False + ), subnets=subnets, ) ) @@ -308,6 +311,7 @@ class VirtualNetwork: id: str name: str location: str + enable_ddos_protection: bool = False subnets: List[VNetSubnet] = None def __post_init__(self): diff --git a/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/__init__.py b/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.metadata.json b/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.metadata.json new file mode 100644 index 0000000000..dfeef103c1 --- /dev/null +++ b/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.metadata.json @@ -0,0 +1,36 @@ +{ + "Provider": "azure", + "CheckID": "network_vnet_ddos_protection_enabled", + "CheckTitle": "Virtual network has Azure DDoS Network Protection enabled", + "CheckType": [], + "ServiceName": "network", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "microsoft.network/virtualnetworks", + "ResourceGroup": "network", + "Description": "**Azure Virtual Networks** are evaluated for **DDoS Network Protection**. When enabled, Azure DDoS Protection provides enhanced mitigation against volumetric, protocol, and application-layer DDoS attacks targeting resources deployed in the virtual network.", + "Risk": "Without DDoS protection, Azure resources are only covered by **DDoS Infrastructure Protection** (basic), which may not mitigate sophisticated or large-scale attacks. Successful DDoS attacks cause **service unavailability** and potential **data exposure** during failover.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://learn.microsoft.com/en-us/azure/ddos-protection/ddos-protection-overview" + ], + "Remediation": { + "Code": { + "CLI": "az network ddos-protection create --resource-group --name && az network vnet update --resource-group --name --ddos-protection-plan ", + "NativeIaC": "```bicep\nresource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' = {\n name: ''\n location: ''\n properties: {\n addressSpace: { addressPrefixes: ['10.0.0.0/16'] }\n enableDdosProtection: true // Critical: enables DDoS Protection\n ddosProtectionPlan: { id: '' }\n }\n}\n```", + "Other": "1. Sign in to Azure portal\n2. Search for 'DDoS protection plans'\n3. Create a new DDoS protection plan\n4. Go to Virtual networks and select the target VNet\n5. Under DDoS protection, select 'Enable' and select the plan\n6. Click Save", + "Terraform": "```hcl\nresource \"azurerm_virtual_network\" \"\" {\n name = \"\"\n location = \"\"\n resource_group_name = \"\"\n address_space = [\"10.0.0.0/16\"]\n\n ddos_protection_plan {\n id = \"\" # Critical: associates DDoS Protection Plan\n enable = true\n }\n}\n```" + }, + "Recommendation": { + "Text": "Enable **Azure DDoS Network Protection** on virtual networks hosting internet-facing workloads. Consider cost implications — DDoS Network Protection has a monthly charge per plan. Use **DDoS IP Protection** as a lower-cost alternative for individual public IPs.", + "Url": "https://hub.prowler.com/check/network_vnet_ddos_protection_enabled" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Azure DDoS Network Protection has a monthly cost (~$2,944/month per plan). Consider whether the workload justifies this cost. Internal-only VNets without internet-facing resources may not need DDoS protection." +} diff --git a/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.py b/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.py new file mode 100644 index 0000000000..6a9a081fb7 --- /dev/null +++ b/prowler/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled.py @@ -0,0 +1,41 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.network.network_client import network_client + + +class network_vnet_ddos_protection_enabled(Check): + """ + Ensure Azure DDoS Network Protection is enabled on virtual networks. + + This check evaluates whether each virtual network has a DDoS protection plan associated, providing always-on traffic monitoring and adaptive mitigation against volumetric and protocol attacks. + + - PASS: The virtual network has DDoS protection enabled. + - FAIL: The virtual network does not have DDoS protection enabled. + """ + + def execute(self) -> Check_Report_Azure: + findings = [] + + for subscription_name, vnets in network_client.virtual_networks.items(): + for vnet in vnets: + report = Check_Report_Azure(metadata=self.metadata(), resource=vnet) + report.subscription = subscription_name + report.resource_name = vnet.name + report.resource_id = vnet.id + report.location = vnet.location + + if vnet.enable_ddos_protection: + report.status = "PASS" + report.status_extended = ( + f"Virtual network '{vnet.name}' has DDoS " + f"protection enabled." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Virtual network '{vnet.name}' does not have " + f"DDoS protection enabled." + ) + + findings.append(report) + + return findings diff --git a/tests/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled_test.py b/tests/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled_test.py new file mode 100644 index 0000000000..f37d3eb781 --- /dev/null +++ b/tests/providers/azure/services/network/network_vnet_ddos_protection_enabled/network_vnet_ddos_protection_enabled_test.py @@ -0,0 +1,99 @@ +from unittest import mock + +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + +VNET_ID = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/test-vnet" + + +class Test_network_vnet_ddos_protection_enabled: + def test_no_subscriptions(self): + network_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.network.network_vnet_ddos_protection_enabled.network_vnet_ddos_protection_enabled.network_client", + new=network_client, + ), + ): + from prowler.providers.azure.services.network.network_vnet_ddos_protection_enabled.network_vnet_ddos_protection_enabled import ( + network_vnet_ddos_protection_enabled, + ) + + network_client.virtual_networks = {} + + check = network_vnet_ddos_protection_enabled() + result = check.execute() + assert len(result) == 0 + + def test_ddos_enabled(self): + network_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.network.network_vnet_ddos_protection_enabled.network_vnet_ddos_protection_enabled.network_client", + new=network_client, + ), + ): + from prowler.providers.azure.services.network.network_vnet_ddos_protection_enabled.network_vnet_ddos_protection_enabled import ( + network_vnet_ddos_protection_enabled, + ) + from prowler.providers.azure.services.network.network_service import ( + VirtualNetwork, + ) + + vnet = VirtualNetwork( + id=VNET_ID, + name="test-vnet", + location="eastus", + enable_ddos_protection=True, + ) + network_client.virtual_networks = {AZURE_SUBSCRIPTION_ID: [vnet]} + + check = network_vnet_ddos_protection_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + + def test_ddos_disabled(self): + network_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.network.network_vnet_ddos_protection_enabled.network_vnet_ddos_protection_enabled.network_client", + new=network_client, + ), + ): + from prowler.providers.azure.services.network.network_vnet_ddos_protection_enabled.network_vnet_ddos_protection_enabled import ( + network_vnet_ddos_protection_enabled, + ) + from prowler.providers.azure.services.network.network_service import ( + VirtualNetwork, + ) + + vnet = VirtualNetwork( + id=VNET_ID, + name="test-vnet", + location="eastus", + enable_ddos_protection=False, + ) + network_client.virtual_networks = {AZURE_SUBSCRIPTION_ID: [vnet]} + + check = network_vnet_ddos_protection_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL"