From 9d5754fc56a01ded935cc17e95057ef3ecbf5d88 Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Tue, 6 Jan 2026 09:20:19 +0530 Subject: [PATCH 1/8] 25533 --- src/powershell/tests/Test-Assessment.25533.md | 10 ++ .../tests/Test-Assessment.25533.ps1 | 130 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/powershell/tests/Test-Assessment.25533.md create mode 100644 src/powershell/tests/Test-Assessment.25533.ps1 diff --git a/src/powershell/tests/Test-Assessment.25533.md b/src/powershell/tests/Test-Assessment.25533.md new file mode 100644 index 000000000..3afeea209 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.25533.md @@ -0,0 +1,10 @@ +DDoS attacks remain a major security and availability risk for customers with cloud-hosted applications. These attacks aim to overwhelm an application's compute, network, or memory resources, rendering it inaccessible to legitimate users. Any public-facing endpoint exposed to the internet can be a potential target for a DDoS attack. Azure DDoS Protection provides always-on monitoring and automatic mitigation against DDoS attacks targeting public-facing workloads. Without Azure DDoS Protection (Network Protection or IP Protection), public IP addresses for services such as Application Gateways, Load Balancers, or virtual machines remain exposed to DDoS attacks that can overwhelm network bandwidth, exhaust system resources, and cause complete service unavailability. These attacks can disrupt access for legitimate users, degrade performance, and create cascading outages across dependent services.Azure DDoS Protection uses adaptive real-time tuning to profile normal traffic patterns and automatically detects anomalies indicative of an attack. When an attack is identified, mitigation is engaged at the Azure network edge to absorb and filter malicious traffic before it reaches your applications. This check verifies that Azure DDoS Protection is enabled for public IP addresses within a VNET, ensuring that applications are protected from DDoS attacks. If this check does not pass, your workloads remain significantly more vulnerable to downtime, customer impact, and operational disruption during an attack. + +## Remediation Resources + +- [Please check the articles below for guidance on how to enable DDoS Protection for Public IP addresses.](https://learn.microsoft.com/en-us/azure/ddos-protection/manage-ddos-protection) +- [Please check the articles below for guidance on how to enable DDoS Protection for Public IP addresses.](https://learn.microsoft.com/en-us/azure/ddos-protection/manage-ddos-ip-protection-portal) + + + +%TestResult% diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 new file mode 100644 index 000000000..00828a66a --- /dev/null +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -0,0 +1,130 @@ +function Test-Assessment-25533 { + [ZtTest( + Category = 'Azure Network Security', + ImplementationCost = 'Low', + MinimumLicense = ('DDoS_Network_Protection', 'DDoS_IP_Protection'), + Pillar = 'Network', + RiskLevel = 'High', + SfiPillar = 'Protect networks', + TenantType = ('Workforce', 'External'), + TestId = 25533, + Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs', + UserImpact = 'Low' + )] + [CmdletBinding()] + param() + + Write-PSFMessage 'Start DDoS Public IP Assessment' -Tag Test -Level VeryVerbose + + if ((Get-MgContext).Environment -ne 'Global') { + Write-PSFMessage "This test is only applicable to the Global environment." -Tag Test -Level VeryVerbose + return + } + + #region Azure Connection Verification + try { + $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction Stop + } + catch { + Write-PSFMessage $_.Exception.Message -Tag Test -Level Error + Add-ZtTestResultDetail -SkippedBecause NoAzureAccess + return + } + #endregion + + #region Data Collection + $subscriptions = Get-AzSubscription + $publicIpFindings = @() + + foreach ($sub in $subscriptions) { + + Set-AzContext -SubscriptionId $sub.Id | Out-Null + + try { + $publicIps = Get-AzPublicIpAddress -ErrorAction Stop + } + catch { + Write-PSFMessage "Unable to list Public IPs in subscription $($sub.Name)." -Tag Test -Level Warning + continue + } + + foreach ($pip in $publicIps) { + + $protectionMode = $null + $source = 'None' + + # Case 1: Explicit DDoS IP protection + if ($pip.DdosSettings -and $pip.DdosSettings.ProtectionMode) { + $protectionMode = $pip.DdosSettings.ProtectionMode + $source = 'PublicIP' + } + + # Case 2: VNET inherited protection + elseif ($pip.IpConfiguration -and $pip.IpConfiguration.Id) { + + try { + $vnet = Get-AzVirtualNetwork -ResourceGroupName $pip.ResourceGroupName -ErrorAction Stop + if ($vnet.EnableDdosProtection) { + $protectionMode = 'VirtualNetworkInherited' + $source = 'VNET' + $vnet.EnableDdosProtection + } + } + catch { + Write-PSFMessage "Failed to resolve VNET for Public IP $($pip.Name)" -Tag Test -Level Debug + } + } + + $isCompliant = $protectionMode -in @('Enabled', 'VirtualNetworkInherited') + + $publicIpFindings += [PSCustomObject]@{ + PublicIpName = $pip.Name + PublicIpId = $pip.Id + Location = $pip.Location + AllocationMethod = $pip.PublicIpAllocationMethod + ProtectionMode = if ($protectionMode) { $protectionMode } else { 'NotEnabled' } + ProtectionSource = $source + IsCompliant = $isCompliant + SubscriptionId = $sub.Id + SubscriptionName = $sub.Name + } + } + } + #endregion + + #region Assessment Logic + if ($publicIpFindings.Count -eq 0) { + Add-ZtTestResultDetail -TestId '25533' -Status $true -Result "No Public IP addresses found." + return + } + + $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } + $passed = ($nonCompliant.Count -eq 0) + + $testResultMarkdown = if ($passed) { + "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" + } + else { + "DDoS Protection is NOT enabled for one or more Public IP addresses.`n`n%TestResult%" + } + #endregion + + #region Result Reporting + $mdInfo = "## Public IP Address DDoS Protection Status`n`n" + $mdInfo += "| Public IP | Location | Allocation | Protection Mode | Source | Status |`n" + $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- |`n" + + foreach ($item in $publicIpFindings | Sort-Object PublicIpName) { + + $icon = if ($item.IsCompliant) { '✅' } else { '❌' } + $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" + $safeName = Get-SafeMarkdown -Text $item.PublicIpName + + $mdInfo += "| $icon [$safeName]($pipLink) | $($item.Location) | $($item.AllocationMethod) | $($item.ProtectionMode) | $($item.ProtectionSource) | $icon |`n" + } + + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo + Add-ZtTestResultDetail -TestId '25533' -Status $passed -Result $testResultMarkdown + #endregion +} From 9fbc8ee88de2cb30f3cd6ca36d8236d45de4117e Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 21 Jan 2026 12:22:49 +0530 Subject: [PATCH 2/8] Code fix --- .../tests/Test-Assessment.25533.ps1 | 149 +++++++++++------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 index 00828a66a..40af1f425 100644 --- a/src/powershell/tests/Test-Assessment.25533.ps1 +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -14,117 +14,148 @@ function Test-Assessment-25533 { [CmdletBinding()] param() - Write-PSFMessage 'Start DDoS Public IP Assessment' -Tag Test -Level VeryVerbose + #region Data Collection + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + $activity = "Checking DDoS Protection is enabled for all Public IP Addresses in VNETs" + Write-ZtProgress -Activity $activity -Status "Checking Azure connection" - if ((Get-MgContext).Environment -ne 'Global') { - Write-PSFMessage "This test is only applicable to the Global environment." -Tag Test -Level VeryVerbose + # Check Azure connection + try { + $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + catch { + Write-PSFMessage $_.Exception.Message -Tag Test -Level Error + } + + if (!$accessToken) { + Write-PSFMessage "Azure authentication token not found." -Level Warning + Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } - #region Azure Connection Verification + # Get Azure subscriptions + Write-ZtProgress -Activity $activity -Status "Getting Azure subscriptions" + + $resourceManagementUrl = (Get-AzContext).Environment.ResourceManagerUrl + $subscriptionsUri = $resourceManagementUrl + 'subscriptions?api-version=2022-12-01' + try { - $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction Stop + $subscriptionsResponse = Invoke-AzRestMethod -Method GET -Uri $subscriptionsUri -ErrorAction Stop + + # Check if the response was successful + if ($subscriptionsResponse.StatusCode -lt 200 -or $subscriptionsResponse.StatusCode -ge 300) { + throw "API returned status code: $($subscriptionsResponse.StatusCode). Content: $($subscriptionsResponse.Content)" + } + + $subscriptions = ($subscriptionsResponse.Content | ConvertFrom-Json).value } catch { - Write-PSFMessage $_.Exception.Message -Tag Test -Level Error + Write-PSFMessage "Failed to retrieve Azure subscriptions: $_" -Level Error Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } - #endregion - #region Data Collection - $subscriptions = Get-AzSubscription + if (!$subscriptions -or $subscriptions.Count -eq 0) { + Write-PSFMessage "No Azure subscriptions found." -Level Warning + $customStatus = 'Investigate' + + } + $publicIpFindings = @() foreach ($sub in $subscriptions) { + Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" - Set-AzContext -SubscriptionId $sub.Id | Out-Null + $publicIpsUri = $resourceManagementUrl + "subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" try { - $publicIps = Get-AzPublicIpAddress -ErrorAction Stop + $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop + + # Check if the response was successful + if ($publicIpsResponse.StatusCode -lt 200 -or $publicIpsResponse.StatusCode -ge 300) { + Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName) - Status Code: $($publicIpsResponse.StatusCode)" -Tag Test -Level Warning + continue + } + + $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value } catch { - Write-PSFMessage "Unable to list Public IPs in subscription $($sub.Name)." -Tag Test -Level Warning + Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName): $_" -Tag Test -Level Warning continue } foreach ($pip in $publicIps) { + $protectionMode = 'Disabled' - $protectionMode = $null - $source = 'None' - - # Case 1: Explicit DDoS IP protection - if ($pip.DdosSettings -and $pip.DdosSettings.ProtectionMode) { - $protectionMode = $pip.DdosSettings.ProtectionMode - $source = 'PublicIP' + # Check ddosSettings property as per API documentation + if ($pip.properties.ddosSettings -and $pip.properties.ddosSettings.protectionMode) { + $protectionMode = $pip.properties.ddosSettings.protectionMode } - # Case 2: VNET inherited protection - elseif ($pip.IpConfiguration -and $pip.IpConfiguration.Id) { - - try { - $vnet = Get-AzVirtualNetwork -ResourceGroupName $pip.ResourceGroupName -ErrorAction Stop - if ($vnet.EnableDdosProtection) { - $protectionMode = 'VirtualNetworkInherited' - $source = 'VNET' - $vnet.EnableDdosProtection - } - } - catch { - Write-PSFMessage "Failed to resolve VNET for Public IP $($pip.Name)" -Tag Test -Level Debug - } - } - - $isCompliant = $protectionMode -in @('Enabled', 'VirtualNetworkInherited') + # Determine compliance: Pass if VirtualNetworkInherited or Enabled + # Fail if Disabled or missing + $isCompliant = $protectionMode -in @('VirtualNetworkInherited', 'Enabled') $publicIpFindings += [PSCustomObject]@{ - PublicIpName = $pip.Name - PublicIpId = $pip.Id - Location = $pip.Location - AllocationMethod = $pip.PublicIpAllocationMethod - ProtectionMode = if ($protectionMode) { $protectionMode } else { 'NotEnabled' } - ProtectionSource = $source + PublicIpName = $pip.name + PublicIpId = $pip.id + Location = $pip.location + ProtectionMode = $protectionMode IsCompliant = $isCompliant - SubscriptionId = $sub.Id - SubscriptionName = $sub.Name + SubscriptionId = $sub.subscriptionId + SubscriptionName = $sub.displayName } } } - #endregion + #endregion Data Collection #region Assessment Logic + # Initialize test variables + $testResultMarkdown = '' + $passed = $false + + # Check if no public IPs found if ($publicIpFindings.Count -eq 0) { Add-ZtTestResultDetail -TestId '25533' -Status $true -Result "No Public IP addresses found." return } + # Determine pass/fail status $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } $passed = ($nonCompliant.Count -eq 0) - $testResultMarkdown = if ($passed) { - "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" + if ($passed) { + $testResultMarkdown = "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" } else { - "DDoS Protection is NOT enabled for one or more Public IP addresses.`n`n%TestResult%" + $testResultMarkdown = "DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" } - #endregion + #endregion Assessment Logic - #region Result Reporting - $mdInfo = "## Public IP Address DDoS Protection Status`n`n" - $mdInfo += "| Public IP | Location | Allocation | Protection Mode | Source | Status |`n" - $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- |`n" - - foreach ($item in $publicIpFindings | Sort-Object PublicIpName) { + #region Report Generation + $mdInfo = "## Public IP Address DDoS Protection Details`n`n" + $mdInfo += "| | Public IP address name and id | DdosSettingsProtectionMode value |`n" + $mdInfo += "| :--- | :--- | :--- |`n" + foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { $icon = if ($item.IsCompliant) { '✅' } else { '❌' } $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" $safeName = Get-SafeMarkdown -Text $item.PublicIpName - $mdInfo += "| $icon [$safeName]($pipLink) | $($item.Location) | $($item.AllocationMethod) | $($item.ProtectionMode) | $($item.ProtectionSource) | $icon |`n" + $mdInfo += "| $icon | [$safeName]($pipLink) | ``$($item.ProtectionMode)`` |`n" } + # Replace the placeholder with detailed information + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo + + # Add test result details - $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo - Add-ZtTestResultDetail -TestId '25533' -Status $passed -Result $testResultMarkdown - #endregion + #endregion Report Generation + $params = @{ + TestId = '25533' + Status = $passed + Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs' + Result = $testResultMarkdown + } + Add-ZtTestResultDetail @params } From 47c672b9bbd95e7010d21e43aad7351065e35da4 Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Tue, 6 Jan 2026 09:20:19 +0530 Subject: [PATCH 3/8] 25533 --- src/powershell/tests/Test-Assessment.25533.md | 10 ++ .../tests/Test-Assessment.25533.ps1 | 130 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/powershell/tests/Test-Assessment.25533.md create mode 100644 src/powershell/tests/Test-Assessment.25533.ps1 diff --git a/src/powershell/tests/Test-Assessment.25533.md b/src/powershell/tests/Test-Assessment.25533.md new file mode 100644 index 000000000..3afeea209 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.25533.md @@ -0,0 +1,10 @@ +DDoS attacks remain a major security and availability risk for customers with cloud-hosted applications. These attacks aim to overwhelm an application's compute, network, or memory resources, rendering it inaccessible to legitimate users. Any public-facing endpoint exposed to the internet can be a potential target for a DDoS attack. Azure DDoS Protection provides always-on monitoring and automatic mitigation against DDoS attacks targeting public-facing workloads. Without Azure DDoS Protection (Network Protection or IP Protection), public IP addresses for services such as Application Gateways, Load Balancers, or virtual machines remain exposed to DDoS attacks that can overwhelm network bandwidth, exhaust system resources, and cause complete service unavailability. These attacks can disrupt access for legitimate users, degrade performance, and create cascading outages across dependent services.Azure DDoS Protection uses adaptive real-time tuning to profile normal traffic patterns and automatically detects anomalies indicative of an attack. When an attack is identified, mitigation is engaged at the Azure network edge to absorb and filter malicious traffic before it reaches your applications. This check verifies that Azure DDoS Protection is enabled for public IP addresses within a VNET, ensuring that applications are protected from DDoS attacks. If this check does not pass, your workloads remain significantly more vulnerable to downtime, customer impact, and operational disruption during an attack. + +## Remediation Resources + +- [Please check the articles below for guidance on how to enable DDoS Protection for Public IP addresses.](https://learn.microsoft.com/en-us/azure/ddos-protection/manage-ddos-protection) +- [Please check the articles below for guidance on how to enable DDoS Protection for Public IP addresses.](https://learn.microsoft.com/en-us/azure/ddos-protection/manage-ddos-ip-protection-portal) + + + +%TestResult% diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 new file mode 100644 index 000000000..00828a66a --- /dev/null +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -0,0 +1,130 @@ +function Test-Assessment-25533 { + [ZtTest( + Category = 'Azure Network Security', + ImplementationCost = 'Low', + MinimumLicense = ('DDoS_Network_Protection', 'DDoS_IP_Protection'), + Pillar = 'Network', + RiskLevel = 'High', + SfiPillar = 'Protect networks', + TenantType = ('Workforce', 'External'), + TestId = 25533, + Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs', + UserImpact = 'Low' + )] + [CmdletBinding()] + param() + + Write-PSFMessage 'Start DDoS Public IP Assessment' -Tag Test -Level VeryVerbose + + if ((Get-MgContext).Environment -ne 'Global') { + Write-PSFMessage "This test is only applicable to the Global environment." -Tag Test -Level VeryVerbose + return + } + + #region Azure Connection Verification + try { + $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction Stop + } + catch { + Write-PSFMessage $_.Exception.Message -Tag Test -Level Error + Add-ZtTestResultDetail -SkippedBecause NoAzureAccess + return + } + #endregion + + #region Data Collection + $subscriptions = Get-AzSubscription + $publicIpFindings = @() + + foreach ($sub in $subscriptions) { + + Set-AzContext -SubscriptionId $sub.Id | Out-Null + + try { + $publicIps = Get-AzPublicIpAddress -ErrorAction Stop + } + catch { + Write-PSFMessage "Unable to list Public IPs in subscription $($sub.Name)." -Tag Test -Level Warning + continue + } + + foreach ($pip in $publicIps) { + + $protectionMode = $null + $source = 'None' + + # Case 1: Explicit DDoS IP protection + if ($pip.DdosSettings -and $pip.DdosSettings.ProtectionMode) { + $protectionMode = $pip.DdosSettings.ProtectionMode + $source = 'PublicIP' + } + + # Case 2: VNET inherited protection + elseif ($pip.IpConfiguration -and $pip.IpConfiguration.Id) { + + try { + $vnet = Get-AzVirtualNetwork -ResourceGroupName $pip.ResourceGroupName -ErrorAction Stop + if ($vnet.EnableDdosProtection) { + $protectionMode = 'VirtualNetworkInherited' + $source = 'VNET' + $vnet.EnableDdosProtection + } + } + catch { + Write-PSFMessage "Failed to resolve VNET for Public IP $($pip.Name)" -Tag Test -Level Debug + } + } + + $isCompliant = $protectionMode -in @('Enabled', 'VirtualNetworkInherited') + + $publicIpFindings += [PSCustomObject]@{ + PublicIpName = $pip.Name + PublicIpId = $pip.Id + Location = $pip.Location + AllocationMethod = $pip.PublicIpAllocationMethod + ProtectionMode = if ($protectionMode) { $protectionMode } else { 'NotEnabled' } + ProtectionSource = $source + IsCompliant = $isCompliant + SubscriptionId = $sub.Id + SubscriptionName = $sub.Name + } + } + } + #endregion + + #region Assessment Logic + if ($publicIpFindings.Count -eq 0) { + Add-ZtTestResultDetail -TestId '25533' -Status $true -Result "No Public IP addresses found." + return + } + + $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } + $passed = ($nonCompliant.Count -eq 0) + + $testResultMarkdown = if ($passed) { + "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" + } + else { + "DDoS Protection is NOT enabled for one or more Public IP addresses.`n`n%TestResult%" + } + #endregion + + #region Result Reporting + $mdInfo = "## Public IP Address DDoS Protection Status`n`n" + $mdInfo += "| Public IP | Location | Allocation | Protection Mode | Source | Status |`n" + $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- |`n" + + foreach ($item in $publicIpFindings | Sort-Object PublicIpName) { + + $icon = if ($item.IsCompliant) { '✅' } else { '❌' } + $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" + $safeName = Get-SafeMarkdown -Text $item.PublicIpName + + $mdInfo += "| $icon [$safeName]($pipLink) | $($item.Location) | $($item.AllocationMethod) | $($item.ProtectionMode) | $($item.ProtectionSource) | $icon |`n" + } + + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo + Add-ZtTestResultDetail -TestId '25533' -Status $passed -Result $testResultMarkdown + #endregion +} From 26a589e70ae31fe875cafa16334f702d95cb3067 Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 21 Jan 2026 12:22:49 +0530 Subject: [PATCH 4/8] Code fix --- .../tests/Test-Assessment.25533.ps1 | 149 +++++++++++------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 index 00828a66a..40af1f425 100644 --- a/src/powershell/tests/Test-Assessment.25533.ps1 +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -14,117 +14,148 @@ function Test-Assessment-25533 { [CmdletBinding()] param() - Write-PSFMessage 'Start DDoS Public IP Assessment' -Tag Test -Level VeryVerbose + #region Data Collection + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + $activity = "Checking DDoS Protection is enabled for all Public IP Addresses in VNETs" + Write-ZtProgress -Activity $activity -Status "Checking Azure connection" - if ((Get-MgContext).Environment -ne 'Global') { - Write-PSFMessage "This test is only applicable to the Global environment." -Tag Test -Level VeryVerbose + # Check Azure connection + try { + $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + catch { + Write-PSFMessage $_.Exception.Message -Tag Test -Level Error + } + + if (!$accessToken) { + Write-PSFMessage "Azure authentication token not found." -Level Warning + Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } - #region Azure Connection Verification + # Get Azure subscriptions + Write-ZtProgress -Activity $activity -Status "Getting Azure subscriptions" + + $resourceManagementUrl = (Get-AzContext).Environment.ResourceManagerUrl + $subscriptionsUri = $resourceManagementUrl + 'subscriptions?api-version=2022-12-01' + try { - $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction Stop + $subscriptionsResponse = Invoke-AzRestMethod -Method GET -Uri $subscriptionsUri -ErrorAction Stop + + # Check if the response was successful + if ($subscriptionsResponse.StatusCode -lt 200 -or $subscriptionsResponse.StatusCode -ge 300) { + throw "API returned status code: $($subscriptionsResponse.StatusCode). Content: $($subscriptionsResponse.Content)" + } + + $subscriptions = ($subscriptionsResponse.Content | ConvertFrom-Json).value } catch { - Write-PSFMessage $_.Exception.Message -Tag Test -Level Error + Write-PSFMessage "Failed to retrieve Azure subscriptions: $_" -Level Error Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } - #endregion - #region Data Collection - $subscriptions = Get-AzSubscription + if (!$subscriptions -or $subscriptions.Count -eq 0) { + Write-PSFMessage "No Azure subscriptions found." -Level Warning + $customStatus = 'Investigate' + + } + $publicIpFindings = @() foreach ($sub in $subscriptions) { + Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" - Set-AzContext -SubscriptionId $sub.Id | Out-Null + $publicIpsUri = $resourceManagementUrl + "subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" try { - $publicIps = Get-AzPublicIpAddress -ErrorAction Stop + $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop + + # Check if the response was successful + if ($publicIpsResponse.StatusCode -lt 200 -or $publicIpsResponse.StatusCode -ge 300) { + Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName) - Status Code: $($publicIpsResponse.StatusCode)" -Tag Test -Level Warning + continue + } + + $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value } catch { - Write-PSFMessage "Unable to list Public IPs in subscription $($sub.Name)." -Tag Test -Level Warning + Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName): $_" -Tag Test -Level Warning continue } foreach ($pip in $publicIps) { + $protectionMode = 'Disabled' - $protectionMode = $null - $source = 'None' - - # Case 1: Explicit DDoS IP protection - if ($pip.DdosSettings -and $pip.DdosSettings.ProtectionMode) { - $protectionMode = $pip.DdosSettings.ProtectionMode - $source = 'PublicIP' + # Check ddosSettings property as per API documentation + if ($pip.properties.ddosSettings -and $pip.properties.ddosSettings.protectionMode) { + $protectionMode = $pip.properties.ddosSettings.protectionMode } - # Case 2: VNET inherited protection - elseif ($pip.IpConfiguration -and $pip.IpConfiguration.Id) { - - try { - $vnet = Get-AzVirtualNetwork -ResourceGroupName $pip.ResourceGroupName -ErrorAction Stop - if ($vnet.EnableDdosProtection) { - $protectionMode = 'VirtualNetworkInherited' - $source = 'VNET' - $vnet.EnableDdosProtection - } - } - catch { - Write-PSFMessage "Failed to resolve VNET for Public IP $($pip.Name)" -Tag Test -Level Debug - } - } - - $isCompliant = $protectionMode -in @('Enabled', 'VirtualNetworkInherited') + # Determine compliance: Pass if VirtualNetworkInherited or Enabled + # Fail if Disabled or missing + $isCompliant = $protectionMode -in @('VirtualNetworkInherited', 'Enabled') $publicIpFindings += [PSCustomObject]@{ - PublicIpName = $pip.Name - PublicIpId = $pip.Id - Location = $pip.Location - AllocationMethod = $pip.PublicIpAllocationMethod - ProtectionMode = if ($protectionMode) { $protectionMode } else { 'NotEnabled' } - ProtectionSource = $source + PublicIpName = $pip.name + PublicIpId = $pip.id + Location = $pip.location + ProtectionMode = $protectionMode IsCompliant = $isCompliant - SubscriptionId = $sub.Id - SubscriptionName = $sub.Name + SubscriptionId = $sub.subscriptionId + SubscriptionName = $sub.displayName } } } - #endregion + #endregion Data Collection #region Assessment Logic + # Initialize test variables + $testResultMarkdown = '' + $passed = $false + + # Check if no public IPs found if ($publicIpFindings.Count -eq 0) { Add-ZtTestResultDetail -TestId '25533' -Status $true -Result "No Public IP addresses found." return } + # Determine pass/fail status $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } $passed = ($nonCompliant.Count -eq 0) - $testResultMarkdown = if ($passed) { - "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" + if ($passed) { + $testResultMarkdown = "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" } else { - "DDoS Protection is NOT enabled for one or more Public IP addresses.`n`n%TestResult%" + $testResultMarkdown = "DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" } - #endregion + #endregion Assessment Logic - #region Result Reporting - $mdInfo = "## Public IP Address DDoS Protection Status`n`n" - $mdInfo += "| Public IP | Location | Allocation | Protection Mode | Source | Status |`n" - $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- |`n" - - foreach ($item in $publicIpFindings | Sort-Object PublicIpName) { + #region Report Generation + $mdInfo = "## Public IP Address DDoS Protection Details`n`n" + $mdInfo += "| | Public IP address name and id | DdosSettingsProtectionMode value |`n" + $mdInfo += "| :--- | :--- | :--- |`n" + foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { $icon = if ($item.IsCompliant) { '✅' } else { '❌' } $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" $safeName = Get-SafeMarkdown -Text $item.PublicIpName - $mdInfo += "| $icon [$safeName]($pipLink) | $($item.Location) | $($item.AllocationMethod) | $($item.ProtectionMode) | $($item.ProtectionSource) | $icon |`n" + $mdInfo += "| $icon | [$safeName]($pipLink) | ``$($item.ProtectionMode)`` |`n" } + # Replace the placeholder with detailed information + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo + + # Add test result details - $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo - Add-ZtTestResultDetail -TestId '25533' -Status $passed -Result $testResultMarkdown - #endregion + #endregion Report Generation + $params = @{ + TestId = '25533' + Status = $passed + Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs' + Result = $testResultMarkdown + } + Add-ZtTestResultDetail @params } From e6fbe2d43a9796abdd5c3290ccab87c71bd1a4cf Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 21 Jan 2026 12:50:10 +0530 Subject: [PATCH 5/8] rebase to main --- .../tests/Test-Assessment.25533.ps1 | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 index 40af1f425..1a7dc8836 100644 --- a/src/powershell/tests/Test-Assessment.25533.ps1 +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -43,7 +43,7 @@ function Test-Assessment-25533 { $subscriptionsResponse = Invoke-AzRestMethod -Method GET -Uri $subscriptionsUri -ErrorAction Stop # Check if the response was successful - if ($subscriptionsResponse.StatusCode -lt 200 -or $subscriptionsResponse.StatusCode -ge 300) { + if ($subscriptionsResponse.StatusCode -ne 200) { throw "API returned status code: $($subscriptionsResponse.StatusCode). Content: $($subscriptionsResponse.Content)" } @@ -57,10 +57,9 @@ function Test-Assessment-25533 { if (!$subscriptions -or $subscriptions.Count -eq 0) { Write-PSFMessage "No Azure subscriptions found." -Level Warning - $customStatus = 'Investigate' - + Add-ZtTestResultDetail -SkippedBecause NoAzureAccess + return } - $publicIpFindings = @() foreach ($sub in $subscriptions) { @@ -72,12 +71,14 @@ function Test-Assessment-25533 { $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop # Check if the response was successful - if ($publicIpsResponse.StatusCode -lt 200 -or $publicIpsResponse.StatusCode -ge 300) { - Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName) - Status Code: $($publicIpsResponse.StatusCode)" -Tag Test -Level Warning - continue + if ($publicIpsResponse.StatusCode -ne 200) { + throw "API returned status code: $($publicIpsResponse.StatusCode). Content: $($publicIpsResponse.Content)" + } + else{ + $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value } - $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value + } catch { Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName): $_" -Tag Test -Level Warning @@ -116,7 +117,7 @@ function Test-Assessment-25533 { # Check if no public IPs found if ($publicIpFindings.Count -eq 0) { - Add-ZtTestResultDetail -TestId '25533' -Status $true -Result "No Public IP addresses found." + Add-ZtTestResultDetail -TestId '25533' -Status $False -Result "No Public IP addresses found." return } @@ -133,8 +134,8 @@ function Test-Assessment-25533 { #endregion Assessment Logic #region Report Generation - $mdInfo = "## Public IP Address DDoS Protection Details`n`n" - $mdInfo += "| | Public IP address name and id | DdosSettingsProtectionMode value |`n" + $mdInfo = "## Summary `n`n" + $mdInfo += "| | Public IP name | DdosSettings protection mode |`n" $mdInfo += "| :--- | :--- | :--- |`n" foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { @@ -142,7 +143,7 @@ function Test-Assessment-25533 { $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" $safeName = Get-SafeMarkdown -Text $item.PublicIpName - $mdInfo += "| $icon | [$safeName]($pipLink) | ``$($item.ProtectionMode)`` |`n" + $mdInfo += "| $icon | [$safeName]($pipLink) | $($item.ProtectionMode) |`n" } # Replace the placeholder with detailed information From c53e08d072856f613f622ca448ab0904d960742c Mon Sep 17 00:00:00 2001 From: Kshitiz sharma Date: Fri, 23 Jan 2026 09:37:31 +0530 Subject: [PATCH 6/8] code fix fpr 25533 --- .../tests/Test-Assessment.25533.ps1 | 259 +++--------------- 1 file changed, 43 insertions(+), 216 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 index 5fb91305d..75c6933f4 100644 --- a/src/powershell/tests/Test-Assessment.25533.ps1 +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -16,255 +16,89 @@ function Test-Assessment-25533 { #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose - $activity = "Checking DDoS Protection is enabled for all Public IP Addresses in VNETs" - Write-ZtProgress -Activity $activity -Status "Checking Azure connection" + $activity = 'Checking DDoS Protection is enabled for all Public IP Addresses in VNETs' + Write-ZtProgress -Activity $activity -Status 'Checking Azure connection' - # Check Azure connection + # --- Azure connection & ARM endpoint --- try { - $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - } - catch { - Write-PSFMessage $_.Exception.Message -Tag Test -Level Error - } + $context = Get-AzContext -ErrorAction Stop - if (!$accessToken) { - Write-PSFMessage "Azure authentication token not found." -Level Warning - Add-ZtTestResultDetail -SkippedBecause NoAzureAccess - return - } - - # Get Azure subscriptions - Write-ZtProgress -Activity $activity -Status "Getting Azure subscriptions" - - $resourceManagementUrl = (Get-AzContext).Environment.ResourceManagerUrl - $subscriptionsUri = $resourceManagementUrl + 'subscriptions?api-version=2022-12-01' - - try { - $subscriptionsResponse = Invoke-AzRestMethod -Method GET -Uri $subscriptionsUri -ErrorAction Stop - - # Check if the response was successful - if ($subscriptionsResponse.StatusCode -lt 200 -or $subscriptionsResponse.StatusCode -ge 300) { - throw "API returned status code: $($subscriptionsResponse.StatusCode). Content: $($subscriptionsResponse.Content)" + if ($context.Environment.Name -ne 'AzureCloud') { + throw "Unsupported Azure environment: $($context.Environment.Name)" } - $subscriptions = ($subscriptionsResponse.Content | ConvertFrom-Json).value + $resourceManagementUrl = $context.Environment.ResourceManagerUrl.TrimEnd('/') } catch { - Write-PSFMessage "Failed to retrieve Azure subscriptions: $_" -Level Error - Add-ZtTestResultDetail -SkippedBecause NoAzureAccess - return - } - - if (!$subscriptions -or $subscriptions.Count -eq 0) { - Write-PSFMessage "No Azure subscriptions found." -Level Warning - $customStatus = 'Investigate' - - } - - $publicIpFindings = @() - - foreach ($sub in $subscriptions) { - Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" - - $publicIpsUri = $resourceManagementUrl + "subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" - - try { - $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop - - # Check if the response was successful - if ($publicIpsResponse.StatusCode -lt 200 -or $publicIpsResponse.StatusCode -ge 300) { - Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName) - Status Code: $($publicIpsResponse.StatusCode)" -Tag Test -Level Warning - continue - } - - $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value - } - catch { - Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName): $_" -Tag Test -Level Warning - continue - } - - foreach ($pip in $publicIps) { - $protectionMode = 'Disabled' - - # Check ddosSettings property as per API documentation - if ($pip.properties.ddosSettings -and $pip.properties.ddosSettings.protectionMode) { - $protectionMode = $pip.properties.ddosSettings.protectionMode - } - - # Determine compliance: Pass if VirtualNetworkInherited or Enabled - # Fail if Disabled or missing - $isCompliant = $protectionMode -in @('VirtualNetworkInherited', 'Enabled') - - $publicIpFindings += [PSCustomObject]@{ - PublicIpName = $pip.name - PublicIpId = $pip.id - Location = $pip.location - ProtectionMode = $protectionMode - IsCompliant = $isCompliant - SubscriptionId = $sub.subscriptionId - SubscriptionName = $sub.displayName - } - } - } - #endregion Data Collection - - #region Assessment Logic - # Initialize test variables - $testResultMarkdown = '' - $passed = $false - - # Check if no public IPs found - if ($publicIpFindings.Count -eq 0) { - Add-ZtTestResultDetail -TestId '25533' -Status $true -Result "No Public IP addresses found." - return - } - - # Determine pass/fail status - $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } - $passed = ($nonCompliant.Count -eq 0) - - if ($passed) { - $testResultMarkdown = "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" - } - else { - $testResultMarkdown = "DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" - } - #endregion Assessment Logic - - #region Report Generation - $mdInfo = "## Public IP Address DDoS Protection Details`n`n" - $mdInfo += "| | Public IP address name and id | DdosSettingsProtectionMode value |`n" - $mdInfo += "| :--- | :--- | :--- |`n" - - foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { - $icon = if ($item.IsCompliant) { '✅' } else { '❌' } - $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" - $safeName = Get-SafeMarkdown -Text $item.PublicIpName - - $mdInfo += "| $icon | [$safeName]($pipLink) | ``$($item.ProtectionMode)`` |`n" - } - - # Replace the placeholder with detailed information - $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo - - # Add test result details - - #endregion Report Generation - $params = @{ - TestId = '25533' - Status = $passed - Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs' - Result = $testResultMarkdown - } - Add-ZtTestResultDetail @params -} - -function Test-Assessment-25533 { - [ZtTest( - Category = 'Azure Network Security', - ImplementationCost = 'Low', - MinimumLicense = ('DDoS_Network_Protection', 'DDoS_IP_Protection'), - Pillar = 'Network', - RiskLevel = 'High', - SfiPillar = 'Protect networks', - TenantType = ('Workforce', 'External'), - TestId = 25533, - Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs', - UserImpact = 'Low' - )] - [CmdletBinding()] - param() - - #region Data Collection - Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose - $activity = "Checking DDoS Protection is enabled for all Public IP Addresses in VNETs" - Write-ZtProgress -Activity $activity -Status "Checking Azure connection" - - # Check Azure connection - try { - $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - } - catch { - Write-PSFMessage $_.Exception.Message -Tag Test -Level Error - } - - if (!$accessToken) { - Write-PSFMessage "Azure authentication token not found." -Level Warning - Add-ZtTestResultDetail -SkippedBecause NoAzureAccess + Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure return } - # Get Azure subscriptions - Write-ZtProgress -Activity $activity -Status "Getting Azure subscriptions" - - $resourceManagementUrl = (Get-AzContext).Environment.ResourceManagerUrl - $subscriptionsUri = $resourceManagementUrl + 'subscriptions?api-version=2022-12-01' + # --- Get subscriptions --- + Write-ZtProgress -Activity $activity -Status 'Getting Azure subscriptions' + $subscriptions = @() try { + $subscriptionsUri = "$resourceManagementUrl/subscriptions?api-version=2022-12-01" $subscriptionsResponse = Invoke-AzRestMethod -Method GET -Uri $subscriptionsUri -ErrorAction Stop - # Check if the response was successful if ($subscriptionsResponse.StatusCode -ne 200) { - throw "API returned status code: $($subscriptionsResponse.StatusCode). Content: $($subscriptionsResponse.Content)" + throw "Failed to retrieve subscriptions. StatusCode=$($subscriptionsResponse.StatusCode)" } $subscriptions = ($subscriptionsResponse.Content | ConvertFrom-Json).value } catch { - Write-PSFMessage "Failed to retrieve Azure subscriptions: $_" -Level Error + Write-PSFMessage $_ -Level Error Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } - if (!$subscriptions -or $subscriptions.Count -eq 0) { - Write-PSFMessage "No Azure subscriptions found." -Level Warning + if (-not $subscriptions -or $subscriptions.Count -eq 0) { Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } + + # --- Collect Public IPs --- $publicIpFindings = @() foreach ($sub in $subscriptions) { Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" - $publicIpsUri = $resourceManagementUrl + "subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" + $publicIpsUri = "$resourceManagementUrl/subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" try { $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop - # Check if the response was successful - if ($publicIpsResponse.StatusCode -ne 200) { - throw "API returned status code: $($publicIpsResponse.StatusCode). Content: $($publicIpsResponse.Content)" - } - else{ - $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value + if ($publicIpsResponse.StatusCode -lt 200 -or $publicIpsResponse.StatusCode -ge 300) { + Write-PSFMessage "Failed to list Public IPs in $($sub.displayName)" -Level Warning + continue } - + $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value } catch { - Write-PSFMessage "Unable to list Public IPs in subscription $($sub.displayName): $_" -Tag Test -Level Warning + Write-PSFMessage $_ -Level Warning continue } foreach ($pip in $publicIps) { - $protectionMode = 'Disabled' - - # Check ddosSettings property as per API documentation - if ($pip.properties.ddosSettings -and $pip.properties.ddosSettings.protectionMode) { - $protectionMode = $pip.properties.ddosSettings.protectionMode + $protectionMode = if ( + $pip.properties.ddosSettings -and + $pip.properties.ddosSettings.protectionMode + ) { + $pip.properties.ddosSettings.protectionMode + } + else { + 'Disabled' } - - # Determine compliance: Pass if VirtualNetworkInherited or Enabled - # Fail if Disabled or missing - $isCompliant = $protectionMode -in @('VirtualNetworkInherited', 'Enabled') $publicIpFindings += [PSCustomObject]@{ PublicIpName = $pip.name PublicIpId = $pip.id Location = $pip.location ProtectionMode = $protectionMode - IsCompliant = $isCompliant + IsCompliant = $protectionMode -in @('VirtualNetworkInherited', 'Enabled') SubscriptionId = $sub.subscriptionId SubscriptionName = $sub.displayName } @@ -273,17 +107,11 @@ function Test-Assessment-25533 { #endregion Data Collection #region Assessment Logic - # Initialize test variables - $testResultMarkdown = '' - $passed = $false - - # Check if no public IPs found if ($publicIpFindings.Count -eq 0) { - Add-ZtTestResultDetail -TestId '25533' -Status $False -Result "No Public IP addresses found." + Add-ZtTestResultDetail -TestId 25533 -Status $false -Result 'No Public IP addresses found.' return } - # Determine pass/fail status $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } $passed = ($nonCompliant.Count -eq 0) @@ -296,29 +124,28 @@ function Test-Assessment-25533 { #endregion Assessment Logic #region Report Generation - $mdInfo = "## Summary `n`n" - $mdInfo += "| | Public IP name | DdosSettings protection mode |`n" + $mdInfo = "## Public IP Address DDoS Protection Details`n`n" + $mdInfo += "| | Public IP address name and id | DdosSettingsProtectionMode value |`n" $mdInfo += "| :--- | :--- | :--- |`n" foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { - $icon = if ($item.IsCompliant) { '✅' } else { '❌' } - $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" + $icon = if ($item.IsCompliant) { '✅' } else { '❌' } + $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" $safeName = Get-SafeMarkdown -Text $item.PublicIpName - $mdInfo += "| $icon | [$safeName]($pipLink) | $($item.ProtectionMode) |`n" + $mdInfo += "| $icon | [$safeName]($pipLink) | ``$($item.ProtectionMode)`` |`n" } - # Replace the placeholder with detailed information $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo - - # Add test result details - #endregion Report Generation + + # --- Final result (splatted) --- $params = @{ - TestId = '25533' + TestId = 25533 Status = $passed - Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs' + Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs' Result = $testResultMarkdown } + Add-ZtTestResultDetail @params } From 0f411f21edcfc5b997f825dc294e45fe936100f3 Mon Sep 17 00:00:00 2001 From: Kshitiz sharma Date: Fri, 23 Jan 2026 14:14:02 +0530 Subject: [PATCH 7/8] Updated code for 25533 -- With fixes --- .../tests/Test-Assessment.25533.ps1 | 153 +++++++++--------- 1 file changed, 79 insertions(+), 74 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 index 75c6933f4..8b6ef9e1d 100644 --- a/src/powershell/tests/Test-Assessment.25533.ps1 +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -14,17 +14,25 @@ function Test-Assessment-25533 { [CmdletBinding()] param() + # ------------------------------- + # State variables + # ------------------------------- + $passed = $null # $true = pass, $false = fail + $testResultMarkdown = '' + $subscriptions = @() + $publicIpFindings = @() + #region Data Collection - Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + Write-PSFMessage 'Start Test-Assessment-25533' -Tag Test -Level VeryVerbose $activity = 'Checking DDoS Protection is enabled for all Public IP Addresses in VNETs' Write-ZtProgress -Activity $activity -Status 'Checking Azure connection' - # --- Azure connection & ARM endpoint --- + # ---- Azure connection ---- try { $context = Get-AzContext -ErrorAction Stop - if ($context.Environment.Name -ne 'AzureCloud') { - throw "Unsupported Azure environment: $($context.Environment.Name)" + if (-not $context.Environment -or $context.Environment.Name -ne 'AzureCloud') { + throw 'Unsupported Azure environment' } $resourceManagementUrl = $context.Environment.ResourceManagerUrl.TrimEnd('/') @@ -34,112 +42,109 @@ function Test-Assessment-25533 { return } - # --- Get subscriptions --- + # ---- Get subscriptions ---- Write-ZtProgress -Activity $activity -Status 'Getting Azure subscriptions' - $subscriptions = @() - try { - $subscriptionsUri = "$resourceManagementUrl/subscriptions?api-version=2022-12-01" - $subscriptionsResponse = Invoke-AzRestMethod -Method GET -Uri $subscriptionsUri -ErrorAction Stop - - if ($subscriptionsResponse.StatusCode -ne 200) { - throw "Failed to retrieve subscriptions. StatusCode=$($subscriptionsResponse.StatusCode)" - } - - $subscriptions = ($subscriptionsResponse.Content | ConvertFrom-Json).value + $uri = "$resourceManagementUrl/subscriptions?api-version=2022-12-01" + $response = Invoke-AzRestMethod -Method GET -Uri $uri -ErrorAction Stop + $subscriptions = @((($response.Content | ConvertFrom-Json).value)) } catch { - Write-PSFMessage $_ -Level Error - Add-ZtTestResultDetail -SkippedBecause NoAzureAccess - return + $passed = $false + $testResultMarkdown = "Failed to retrieve Azure subscriptions: $($_.Exception.Message)" } - if (-not $subscriptions -or $subscriptions.Count -eq 0) { - Add-ZtTestResultDetail -SkippedBecause NoAzureAccess - return + # ❌ Rule: no subscriptions → FAIL + if ($passed -ne $false -and $subscriptions.Count -eq 0) { + $passed = $false + $testResultMarkdown = 'No Azure subscriptions were returned. The assessment cannot be evaluated.' } - # --- Collect Public IPs --- - $publicIpFindings = @() - - foreach ($sub in $subscriptions) { - Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" - - $publicIpsUri = "$resourceManagementUrl/subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" + # ---- Collect Public IPs ---- + if ($passed -ne $false) { + foreach ($sub in $subscriptions) { + Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" - try { - $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop - - if ($publicIpsResponse.StatusCode -lt 200 -or $publicIpsResponse.StatusCode -ge 300) { - Write-PSFMessage "Failed to list Public IPs in $($sub.displayName)" -Level Warning + try { + $publicIpsUri = "$resourceManagementUrl/subscriptions/$($sub.subscriptionId)/providers/Microsoft.Network/publicIPAddresses?api-version=2025-03-01" + $publicIpsResponse = Invoke-AzRestMethod -Method GET -Uri $publicIpsUri -ErrorAction Stop + } + catch { + Write-PSFMessage "Error calling Public IP API for $($sub.displayName): $($_.Exception.Message)" -Level Warning continue } - $publicIps = ($publicIpsResponse.Content | ConvertFrom-Json).value - } - catch { - Write-PSFMessage $_ -Level Warning - continue - } + $body = $publicIpsResponse.Content | ConvertFrom-Json + $publicIps = $body.value - foreach ($pip in $publicIps) { - $protectionMode = if ( - $pip.properties.ddosSettings -and - $pip.properties.ddosSettings.protectionMode - ) { - $pip.properties.ddosSettings.protectionMode - } - else { - 'Disabled' + # Skip subscription if no Public IPs or no permission + if (-not $publicIps) { + continue } - $publicIpFindings += [PSCustomObject]@{ - PublicIpName = $pip.name - PublicIpId = $pip.id - Location = $pip.location - ProtectionMode = $protectionMode - IsCompliant = $protectionMode -in @('VirtualNetworkInherited', 'Enabled') - SubscriptionId = $sub.subscriptionId - SubscriptionName = $sub.displayName + foreach ($pip in $publicIps) { + $mode = if ( + $pip.properties.ddosSettings -and + $pip.properties.ddosSettings.protectionMode + ) { + $pip.properties.ddosSettings.protectionMode + } + else { + 'Disabled' + } + + $publicIpFindings += [PSCustomObject]@{ + PublicIpName = $pip.name + PublicIpId = $pip.id + ProtectionMode = $mode + IsCompliant = $mode -in @('VirtualNetworkInherited', 'Enabled') + SubscriptionName = $sub.displayName + } } } } #endregion Data Collection #region Assessment Logic - if ($publicIpFindings.Count -eq 0) { - Add-ZtTestResultDetail -TestId 25533 -Status $false -Result 'No Public IP addresses found.' - return - } + if ($passed -ne $false) { - $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } - $passed = ($nonCompliant.Count -eq 0) + # ✅ Subscriptions exist but NO Public IPs anywhere + # → NO OUTPUT (silent exit) + if ($publicIpFindings.Count -eq 0) { + return + } - if ($passed) { - $testResultMarkdown = "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" - } - else { - $testResultMarkdown = "DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" + $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } + + if ($nonCompliant.Count -eq 0) { + $passed = $true + $testResultMarkdown = "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" + } + else { + $passed = $false + $testResultMarkdown = "DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" + } } #endregion Assessment Logic #region Report Generation - $mdInfo = "## Public IP Address DDoS Protection Details`n`n" - $mdInfo += "| | Public IP address name and id | DdosSettingsProtectionMode value |`n" + $mdInfo = "## Public IP Address DDoS Protection Details`n`n" + $mdInfo += "| | Public IP name | Protection Mode |`n" $mdInfo += "| :--- | :--- | :--- |`n" foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { - $icon = if ($item.IsCompliant) { '✅' } else { '❌' } - $pipLink = "https://portal.azure.com/#@/resource$($item.PublicIpId)" - $safeName = Get-SafeMarkdown -Text $item.PublicIpName + $icon = if ($item.IsCompliant) { '✅' } else { '❌' } + $link = "https://portal.azure.com/#@/resource$($item.PublicIpId)" - $mdInfo += "| $icon | [$safeName]($pipLink) | ``$($item.ProtectionMode)`` |`n" + $mdInfo += "| $icon | [$($item.PublicIpName)]($link) | $($item.ProtectionMode) |`n" } $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation - # --- Final result (splatted) --- + # ------------------------------- + # Final result emission (ONCE) + # ------------------------------- $params = @{ TestId = 25533 Status = $passed From b0145bb25e35a74ab849955e33b854d167df00a7 Mon Sep 17 00:00:00 2001 From: Kshitiz sharma Date: Wed, 28 Jan 2026 22:09:03 +0530 Subject: [PATCH 8/8] Code update as per spec --- .../tests/Test-Assessment.25533.ps1 | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.25533.ps1 b/src/powershell/tests/Test-Assessment.25533.ps1 index 8b6ef9e1d..3550af59e 100644 --- a/src/powershell/tests/Test-Assessment.25533.ps1 +++ b/src/powershell/tests/Test-Assessment.25533.ps1 @@ -14,19 +14,18 @@ function Test-Assessment-25533 { [CmdletBinding()] param() - # ------------------------------- - # State variables - # ------------------------------- - $passed = $null # $true = pass, $false = fail - $testResultMarkdown = '' - $subscriptions = @() - $publicIpFindings = @() - #region Data Collection - Write-PSFMessage 'Start Test-Assessment-25533' -Tag Test -Level VeryVerbose + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + $activity = 'Checking DDoS Protection is enabled for all Public IP Addresses in VNETs' Write-ZtProgress -Activity $activity -Status 'Checking Azure connection' + # Initialize test variables + $passed = $null + $testResultMarkdown = '' + $subscriptions = @() + $publicIpFindings = @() + # ---- Azure connection ---- try { $context = Get-AzContext -ErrorAction Stop @@ -38,6 +37,7 @@ function Test-Assessment-25533 { $resourceManagementUrl = $context.Environment.ResourceManagerUrl.TrimEnd('/') } catch { + #----- Scenario : Not connected to Azure ----- Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure return } @@ -50,18 +50,12 @@ function Test-Assessment-25533 { $subscriptions = @((($response.Content | ConvertFrom-Json).value)) } catch { - $passed = $false $testResultMarkdown = "Failed to retrieve Azure subscriptions: $($_.Exception.Message)" - } - - # ❌ Rule: no subscriptions → FAIL - if ($passed -ne $false -and $subscriptions.Count -eq 0) { $passed = $false - $testResultMarkdown = 'No Azure subscriptions were returned. The assessment cannot be evaluated.' } - # ---- Collect Public IPs ---- - if ($passed -ne $false) { + # ---- Collect Public IPs from subscriptions ---- + if ($passed -ne $false -and $subscriptions.Count -gt 0) { foreach ($sub in $subscriptions) { Write-ZtProgress -Activity $activity -Status "Processing subscription: $($sub.displayName)" @@ -77,11 +71,13 @@ function Test-Assessment-25533 { $body = $publicIpsResponse.Content | ConvertFrom-Json $publicIps = $body.value - # Skip subscription if no Public IPs or no permission + #----- Scenario : No Public IPs found (Skip)----- + # if (-not $publicIps) { continue } + # Collect Public IPs from this subscription foreach ($pip in $publicIps) { $mode = if ( $pip.properties.ddosSettings -and @@ -106,49 +102,57 @@ function Test-Assessment-25533 { #endregion Data Collection #region Assessment Logic - if ($passed -ne $false) { - - # ✅ Subscriptions exist but NO Public IPs anywhere - # → NO OUTPUT (silent exit) - if ($publicIpFindings.Count -eq 0) { - return - } + if ($subscriptions.Count -eq 0) { + #----- Scenario : No subscriptions found - Skip + $testResultMarkdown = 'The Signed in user does not have any active Azure subscription to perform test.' + Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure -Result $testResultMarkdown + return + } + elseif ($publicIpFindings.Count -eq 0) { + #----- Scenario :Subscriptions exist but no Public IPs - SKIP + $testResultMarkdown = 'No Public IP addresses were found in any subscriptions.' + Add-ZtTestResultDetail -SkippedBecause NotSupported -Result $testResultMarkdown + return + } + else { + # Public IPs found - evaluate compliance $nonCompliant = $publicIpFindings | Where-Object { -not $_.IsCompliant } if ($nonCompliant.Count -eq 0) { $passed = $true - $testResultMarkdown = "DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" + $testResultMarkdown = "✅ DDoS Protection is enabled for all Public IP addresses.`n`n%TestResult%" } else { $passed = $false - $testResultMarkdown = "DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" + $testResultMarkdown = "❌ DDoS Protection is not enabled for one or more Public IP addresses.`n`n%TestResult%" } } #endregion Assessment Logic #region Report Generation - $mdInfo = "## Public IP Address DDoS Protection Details`n`n" - $mdInfo += "| | Public IP name | Protection Mode |`n" - $mdInfo += "| :--- | :--- | :--- |`n" + $mdInfo = '' + + if ($publicIpFindings.Count -gt 0) { + $mdInfo = "## Public IP Address DDoS Protection Details`n`n" + $mdInfo += "| | Public IP name | Protection Mode |`n" + $mdInfo += "| :--- | :--- | :--- |`n" - foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { - $icon = if ($item.IsCompliant) { '✅' } else { '❌' } - $link = "https://portal.azure.com/#@/resource$($item.PublicIpId)" + foreach ($item in $publicIpFindings | Sort-Object IsCompliant, PublicIpName) { + $icon = if ($item.IsCompliant) { '✅' } else { '❌' } + $link = "https://portal.azure.com/#@/resource$($item.PublicIpId)" - $mdInfo += "| $icon | [$($item.PublicIpName)]($link) | $($item.ProtectionMode) |`n" + $mdInfo += "| $icon | [$($item.PublicIpName)]($link) | $($item.ProtectionMode) |`n" + } } $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation - # ------------------------------- - # Final result emission (ONCE) - # ------------------------------- $params = @{ - TestId = 25533 - Status = $passed + TestId = '25533' Title = 'DDoS Protection is enabled for all Public IP Addresses in VNETs' + Status = $passed Result = $testResultMarkdown }