From 98b5bac5fc25356efd92cc3d9cdfe2f4b58df887 Mon Sep 17 00:00:00 2001 From: aav-wh <2d9c6kh58x@privaterelay.appleid.com> Date: Sat, 30 May 2026 06:20:50 +0100 Subject: [PATCH] feat(scanner): add AZ-NET-014 VNet peering gateway transit rule - Add scanner/rules/az_net_014.py to detect VNet peerings with allowGatewayTransit or useRemoteGateways enabled - Add get_vnet_peerings() and get_azure_firewalls() to azure_client.py - Add playbooks/cli/fix_az_net_014.sh remediation script - Update all 4 compliance framework JSONs with AZ-NET-014 mappings --- .../frameworks/cis_azure_benchmark.json | 5 ++ compliance/frameworks/iso27001.json | 5 ++ compliance/frameworks/nist_csf.json | 5 ++ compliance/frameworks/soc2.json | 5 ++ playbooks/cli/fix_az_net_014.sh | 22 +++++++ scanner/azure_client.py | 19 +++++- scanner/rules/az_net_014.py | 58 +++++++++++++++++++ 7 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 playbooks/cli/fix_az_net_014.sh create mode 100644 scanner/rules/az_net_014.py diff --git a/compliance/frameworks/cis_azure_benchmark.json b/compliance/frameworks/cis_azure_benchmark.json index d0d636d..f46959c 100644 --- a/compliance/frameworks/cis_azure_benchmark.json +++ b/compliance/frameworks/cis_azure_benchmark.json @@ -162,6 +162,11 @@ "control_id": "8.5", "control_name": "Ensure that the expiration date is set on all certificates", "description": "A certificate stored in Azure Key Vault is expiring within 30 days and does not have auto-renewal configured. CIS 8.5 requires that expiration dates are monitored and certificates are renewed before expiry to prevent service outages and broken authentication flows." + }, + "AZ-NET-014": { + "control_id": "6.4", + "control_name": "Ensure that Azure Firewall is enabled on Virtual Networks", + "description": "VNet peering connections with allowGatewayTransit or useRemoteGateways enabled allow traffic to route between network segments through shared gateways. This can break network segmentation and enable lateral movement between zones that should remain isolated. Peering connections should be reviewed and gateway transit disabled unless explicitly required and documented." } } } diff --git a/compliance/frameworks/iso27001.json b/compliance/frameworks/iso27001.json index 87647ff..c4a9814 100644 --- a/compliance/frameworks/iso27001.json +++ b/compliance/frameworks/iso27001.json @@ -167,6 +167,11 @@ "control_id": "A.9.2.3", "control_name": "Management of privileged access rights", "description": "The allocation and use of privileged access rights should be restricted and controlled. PIM enforces just-in-time access with time limits and approval workflows, ensuring privileged access rights are tightly managed and not permanently assigned." + }, + "AZ-NET-014": { + "control_id": "A.13.1.1", + "control_name": "Network controls", + "description": "VNet peering connections with gateway transit enabled allow traffic to flow between network segments through shared gateways, potentially bypassing network controls. Networks should be managed and controlled to protect information in systems and applications. Gateway transit on peering connections should be disabled unless explicitly required." } } } diff --git a/compliance/frameworks/nist_csf.json b/compliance/frameworks/nist_csf.json index 365faae..6acbd25 100644 --- a/compliance/frameworks/nist_csf.json +++ b/compliance/frameworks/nist_csf.json @@ -162,6 +162,11 @@ "control_id": "PR.AC-4", "control_name": "Access permissions and authorizations are managed", "description": "PIM ensures privileged access permissions are managed with time-bound activation and approval workflows. Without PIM, permanently assigned admin roles violate the principle of least privilege and increase the blast radius of compromised accounts." + }, + "AZ-NET-014": { + "control_id": "PR.AC-5", + "control_name": "Network integrity is protected", + "description": "VNet peering with gateway transit enabled allows traffic to cross network boundaries through shared gateways, undermining network segmentation. PR.AC-5 requires that network integrity is protected. Disabling gateway transit on peering connections enforces boundary integrity between network zones." } } } diff --git a/compliance/frameworks/soc2.json b/compliance/frameworks/soc2.json index 8fcc996..0b1fb6e 100644 --- a/compliance/frameworks/soc2.json +++ b/compliance/frameworks/soc2.json @@ -162,6 +162,11 @@ "control_id": "CC6.3", "control_name": "Role-based access control", "description": "PIM provides role-based access control with time-bound activation for privileged roles. Without PIM, admin roles are permanently assigned with no controls, violating the requirement for managed and restricted privileged access." + }, + "AZ-NET-014": { + "control_id": "CC6.6", + "control_name": "Restricts Access from Outside the Network Boundary", + "description": "VNet peering with allowGatewayTransit or useRemoteGateways enabled allows traffic to cross network boundaries through shared gateways, weakening the logical separation between network zones. CC6.6 requires that logical access from outside the network boundary is restricted and controlled. Gateway transit on peering connections should be disabled to enforce boundary separation." } } } diff --git a/playbooks/cli/fix_az_net_014.sh b/playbooks/cli/fix_az_net_014.sh new file mode 100644 index 0000000..d607033 --- /dev/null +++ b/playbooks/cli/fix_az_net_014.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +RESOURCE_GROUP=$1 +VNET_NAME=$2 +PEERING_NAME=$3 + +if [ -z "$RESOURCE_GROUP" ] || [ -z "$VNET_NAME" ] || [ -z "$PEERING_NAME" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Disabling gateway transit on peering: $PEERING_NAME" + +az network vnet peering update \ + --resource-group "$RESOURCE_GROUP" \ + --vnet-name "$VNET_NAME" \ + --name "$PEERING_NAME" \ + --set allowGatewayTransit=false useRemoteGateways=false + +echo "Done. Gateway transit disabled on peering: $PEERING_NAME" +echo "Note: Verify that disabling gateway transit does not break any intended routing before applying to production." \ No newline at end of file diff --git a/scanner/azure_client.py b/scanner/azure_client.py index 4b932ce..14c9799 100644 --- a/scanner/azure_client.py +++ b/scanner/azure_client.py @@ -240,7 +240,6 @@ def get_virtual_networks(self) -> List[Any]: logger.error("get_virtual_networks failed: %s", exc) return [] - def get_public_ip_addresses(self) -> List[Any]: """List all public IP addresses in the subscription.""" try: @@ -250,6 +249,24 @@ def get_public_ip_addresses(self) -> List[Any]: logger.error("get_public_ip_addresses failed: %s", exc) return [] + def get_azure_firewalls(self, resource_group: str) -> List[Any]: + """List all Azure Firewalls in a resource group.""" + try: + client = NetworkManagementClient(self.credential, self.subscription_id) + return list(client.azure_firewalls.list(resource_group)) + except Exception as exc: + logger.error("get_azure_firewalls(%s) failed: %s", resource_group, exc) + return [] + + def get_vnet_peerings(self, resource_group: str, vnet_name: str) -> List[Any]: + """List all peering connections for a Virtual Network.""" + try: + client = NetworkManagementClient(self.credential, self.subscription_id) + return list(client.virtual_network_peerings.list(resource_group, vnet_name)) + except Exception as exc: + logger.error("get_vnet_peerings(%s) failed: %s", vnet_name, exc) + return [] + # ------------------------------------------------------------------ # # Compute # # ------------------------------------------------------------------ # diff --git a/scanner/rules/az_net_014.py b/scanner/rules/az_net_014.py new file mode 100644 index 0000000..614b1cf --- /dev/null +++ b/scanner/rules/az_net_014.py @@ -0,0 +1,58 @@ +"""AZ-NET-014: VNet peering configured without gateway transit restrictions.""" +from typing import Any, Dict, List + +RULE_ID = "AZ-NET-014" +RULE_NAME = "VNet Peering Configured Without Gateway Transit Restrictions" +SEVERITY = "MEDIUM" +CATEGORY = "Network" +FRAMEWORKS = { + "CIS": "6.4", + "NIST": "PR.AC-5", + "ISO27001": "A.13.1.1", + "SOC2": "CC6.6" +} +DESCRIPTION = ( + "A Virtual Network peering connection has gateway transit enabled. " + "Enabling allowGatewayTransit or useRemoteGateways on a peering " + "connection allows traffic to flow between network segments through " + "shared gateways, potentially enabling lateral movement between " + "network zones that should be isolated from each other." +) +REMEDIATION = ( + "Review all VNet peering connections and disable allowGatewayTransit " + "and useRemoteGateways unless explicitly required and documented. " + "Ensure peering connections follow the principle of least privilege " + "and only permit the minimum required traffic between networks." +) +PLAYBOOK = "playbooks/cli/fix_az_net_014.sh" + + +def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]: + findings: List[Dict[str, Any]] = [] + for vnet in azure_client.get_virtual_networks(): + parsed = azure_client.parse_resource_id(vnet.id) + resource_group = parsed["resource_group"] + vnet_name = parsed["name"] + peerings = azure_client.get_vnet_peerings(resource_group, vnet_name) + for peering in peerings: + allow_gateway_transit = getattr(peering, "allow_gateway_transit", False) + use_remote_gateways = getattr(peering, "use_remote_gateways", False) + if allow_gateway_transit or use_remote_gateways: + findings.append({ + "rule_id": RULE_ID, + "rule_name": RULE_NAME, + "severity": SEVERITY, + "category": CATEGORY, + "resource_id": vnet.id, + "resource_name": vnet_name, + "resource_type": "Microsoft.Network/virtualNetworks", + "description": DESCRIPTION, + "remediation": REMEDIATION, + "playbook": PLAYBOOK, + "frameworks": FRAMEWORKS, + "metadata": { + "resource_group": resource_group, + "peering_name": getattr(peering, "name", "unknown") + } + }) + return findings \ No newline at end of file