tests/Test-Assessment.26884.ps1
|
<#
.SYNOPSIS Validates that bot protection ruleset is enabled and assigned in Azure Front Door WAF. .DESCRIPTION This test evaluates Azure Front Door Premium profiles to ensure the Bot Manager rule set is enabled in the associated WAF policy and properly assigned via security policies. .NOTES Test ID: 26884 Category: Azure Network Security Required APIs: Azure Management REST API (subscriptions, Front Door profiles, WAF policies, security policies) #> function Test-Assessment-26884 { [ZtTest( Category = 'Azure Network Security', ImplementationCost = 'Low', MinimumLicense = ('Azure_Front_Door_Premium'), Pillar = 'Network', RiskLevel = 'High', SfiPillar = 'Protect networks', TenantType = ('Workforce'), TestId = 26884, Title = 'Bot protection ruleset is enabled and assigned in Azure Front Door WAF', UserImpact = 'Low' )] [CmdletBinding()] param() #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Evaluating Azure Front Door WAF bot protection configuration' # Check if connected to Azure Write-ZtProgress -Activity $activity -Status 'Checking Azure connection' $azContext = Get-AzContext -ErrorAction SilentlyContinue if (-not $azContext) { Write-PSFMessage 'Not connected to Azure.' -Level Warning Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure return } # Check the supported environment Write-ZtProgress -Activity $activity -Status 'Checking Azure environment' if ($azContext.Environment.Name -ne 'AzureCloud') { Write-PSFMessage 'This test is only applicable to the AzureCloud environment.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotSupported return } # Check Azure access token try { $accessToken = Get-AzAccessToken -AsSecureString -ErrorAction SilentlyContinue -WarningAction SilentlyContinue } catch { Write-PSFMessage $_.Exception.Message -Tag Test -Level Error } if (-not $accessToken) { Write-PSFMessage 'Azure authentication token not found.' -Tag Test -Level Warning Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure return } # Q1 & Q2: Query Azure Front Door Premium profiles using Azure Resource Graph Write-ZtProgress -Activity $activity -Status 'Querying Azure Front Door profiles via Resource Graph' $argQuery = @" resources | where type =~ 'microsoft.cdn/profiles' | where properties.provisioningState =~ 'Succeeded' | where sku.name in~ ('Premium_AzureFrontDoor', 'Standard_AzureFrontDoor') | join kind=leftouter ( resourcecontainers | where type =~ 'microsoft.resources/subscriptions' | project subscriptionName=name, subscriptionId ) on subscriptionId | project ProfileName=name, ProfileId=id, Location=location, SkuName=tostring(sku.name), ResourceGroup=resourceGroup, SubscriptionId=subscriptionId, SubscriptionName=subscriptionName "@ $allFrontDoorProfiles = @() try { $allFrontDoorProfiles = @(Invoke-ZtAzureResourceGraphRequest -Query $argQuery) Write-PSFMessage "ARG Query returned $($allFrontDoorProfiles.Count) Azure Front Door profile(s)" -Tag Test -Level VeryVerbose } catch { Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning Add-ZtTestResultDetail -SkippedBecause NotSupported return } # Check if any Azure Front Door profiles exist if ($allFrontDoorProfiles.Count -eq 0) { Write-PSFMessage 'No Azure Front Door profiles found.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotApplicable return } # Filter Premium profiles for evaluation (Standard profiles will be marked as skipped) $premiumProfiles = $allFrontDoorProfiles | Where-Object { $_.SkuName -eq 'Premium_AzureFrontDoor' } $standardProfiles = $allFrontDoorProfiles | Where-Object { $_.SkuName -eq 'Standard_AzureFrontDoor' } # If no Premium profiles exist, skip the test if ($premiumProfiles.Count -eq 0) { Write-PSFMessage 'No Azure Front Door Premium profiles found. Bot protection is only available in Premium SKU.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotApplicable return } # Q3: Query all WAF policies in all subscriptions Write-ZtProgress -Activity $activity -Status 'Querying WAF policies' $wafPoliciesArgQuery = @" resources | where type =~ 'microsoft.network/frontdoorwebapplicationfirewallpolicies' | project PolicyId=id, PolicyName=name, SkuName=tostring(sku.name), EnabledState=tostring(properties.policySettings.enabledState), ManagedRuleSets=properties.managedRules.managedRuleSets, SecurityPolicyLinks=properties.securityPolicyLinks, SubscriptionId=subscriptionId "@ $allWafPolicies = @() try { $allWafPolicies = @(Invoke-ZtAzureResourceGraphRequest -Query $wafPoliciesArgQuery) Write-PSFMessage "ARG Query returned $($allWafPolicies.Count) WAF policy(ies)" -Tag Test -Level VeryVerbose } catch { Write-PSFMessage "WAF policies query failed: $($_.Exception.Message)" -Tag Test -Level Warning Add-ZtTestResultDetail -SkippedBecause NotSupported return } # Build a hashtable for quick WAF policy lookup by ID $wafPolicyLookup = @{} foreach ($wafPolicy in $allWafPolicies) { $wafPolicyLookup[$wafPolicy.PolicyId.ToLower()] = $wafPolicy } # Evaluate each Premium Front Door profile Write-ZtProgress -Activity $activity -Status 'Evaluating bot protection configuration' $evaluationResults = @() foreach ($fdProfile in $premiumProfiles) { $profileId = $fdProfile.ProfileId $profileName = $fdProfile.ProfileName # Q4: Query security policies for this Front Door profile $securityPoliciesPath = "$profileId/securityPolicies?api-version=2024-02-01" $securityPolicies = @() try { $securityPolicies = @(Invoke-ZtAzureRequest -Path $securityPoliciesPath) } catch { Write-PSFMessage "Error querying security policies for $profileName : $_" -Level Warning } # Evaluate security policies and associated WAF policies $hasValidBotProtection = $false $associatedWafPolicyName = 'None' $associatedWafPolicyId = $null $botManagerEnabled = 'No' $ruleSetVersion = 'N/A' $ruleSetAction = 'N/A' $domainsProtected = 0 $securityPolicyConfigured = 'No' $wafEnabled = 'N/A' if ($securityPolicies.Count -gt 0) { $securityPolicyConfigured = 'Yes' foreach ($secPolicy in $securityPolicies) { # Reset domain count for each security policy to avoid accumulation $currentPolicyDomainCount = 0 $wafPolicyRef = $secPolicy.properties.parameters.wafPolicy.id if ($wafPolicyRef) { $associatedWafPolicyId = $wafPolicyRef.ToLower() # Count domains protected by this security policy $associations = $secPolicy.properties.parameters.associations if ($associations) { foreach ($assoc in $associations) { if ($assoc.domains) { $currentPolicyDomainCount += $assoc.domains.Count } } } # Look up the WAF policy if ($wafPolicyLookup.ContainsKey($associatedWafPolicyId)) { $wafPolicy = $wafPolicyLookup[$associatedWafPolicyId] $associatedWafPolicyName = $wafPolicy.PolicyName $wafEnabled = $wafPolicy.EnabledState $wafIsPremium = $wafPolicy.SkuName -eq 'Premium_AzureFrontDoor' # Check for Bot Manager rule set $managedRuleSets = $wafPolicy.ManagedRuleSets if ($managedRuleSets) { foreach ($ruleSet in $managedRuleSets) { if ($ruleSet.ruleSetType -eq 'Microsoft_BotManagerRuleSet') { $botManagerEnabled = 'Yes' $ruleSetVersion = $ruleSet.ruleSetVersion if ($ruleSet.ruleSetAction) { $ruleSetAction = $ruleSet.ruleSetAction } else { $ruleSetAction = 'Per-rule defaults' } # Check if WAF policy is enabled and Bot Manager is present if ($wafIsPremium -and $wafEnabled -eq 'Enabled') { $hasValidBotProtection = $true # Only count domains from security policy with valid bot protection $domainsProtected = $currentPolicyDomainCount } break } } } } } # Stop iterating if valid bot protection is found to preserve the correct WAF policy details if ($hasValidBotProtection) { break } } } $status = if ($hasValidBotProtection) { 'Pass' } else { 'Fail' } $evaluationResults += [PSCustomObject]@{ SubscriptionId = $fdProfile.SubscriptionId SubscriptionName = $fdProfile.SubscriptionName ProfileName = $profileName ProfileId = $profileId SkuName = $fdProfile.SkuName WAFPolicyName = $associatedWafPolicyName WAFEnabled = $wafEnabled SecurityPolicyConfigured = $securityPolicyConfigured BotManagerEnabled = $botManagerEnabled RuleSetVersion = $ruleSetVersion RuleSetAction = $ruleSetAction DomainsProtected = $domainsProtected Status = $status } } # Add Standard SKU profiles as skipped $skippedResults = @() foreach ($fdProfile in $standardProfiles) { $skippedResults += [PSCustomObject]@{ SubscriptionId = $fdProfile.SubscriptionId SubscriptionName = $fdProfile.SubscriptionName ProfileName = $fdProfile.ProfileName ProfileId = $fdProfile.ProfileId SkuName = $fdProfile.SkuName WAFPolicyName = 'N/A' WAFEnabled = 'N/A' SecurityPolicyConfigured = 'N/A' BotManagerEnabled = 'N/A' RuleSetVersion = 'N/A' RuleSetAction = 'N/A' DomainsProtected = 0 Status = 'Skipped' } } #endregion Data Collection #region Assessment Logic $passedItems = $evaluationResults | Where-Object { $_.Status -eq 'Pass' } $failedItems = $evaluationResults | Where-Object { $_.Status -eq 'Fail' } $passed = ($failedItems.Count -eq 0) -and ($passedItems.Count -gt 0) if ($passed) { $testResultMarkdown = "✅ Bot protection ruleset is enabled and assigned to Azure Front Door WAF, providing protection against malicious bot traffic.`n`n%TestResult%" } else { $testResultMarkdown = "❌ Bot protection ruleset is not enabled or not assigned to Azure Front Door WAF, leaving web applications vulnerable to automated attacks and malicious bots.`n`n%TestResult%" } #endregion Assessment Logic #region Report Generation # Portal link variables $portalFrontDoorBrowseLink = 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.Cdn%2Fprofiles' $portalSubscriptionBaseLink = 'https://portal.azure.com/#resource/subscriptions' $portalResourceBaseLink = 'https://portal.azure.com/#resource' $mdInfo = "`n## [Azure Front Door WAF bot protection status]($portalFrontDoorBrowseLink)`n`n" # Premium profiles table if ($evaluationResults.Count -gt 0) { $tableRows = "" $formatTemplate = @' | Subscription | Profile name | SKU | WAF policy | Bot protection enabled | Rule set version | Rule set action | Domains protected | Status | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | {0} '@ foreach ($result in $evaluationResults) { $subscriptionLink = "[$(Get-SafeMarkdown $result.SubscriptionName)]($portalSubscriptionBaseLink/$($result.SubscriptionId)/overview)" $profileLink = "[$(Get-SafeMarkdown $result.ProfileName)]($portalResourceBaseLink$($result.ProfileId)/securityPolicies)" $statusText = if ($result.Status -eq 'Pass') { '✅ Pass' } else { '❌ Fail' } $tableRows += "| $subscriptionLink | $profileLink | $($result.SkuName) | $(Get-SafeMarkdown $result.WAFPolicyName) | $($result.BotManagerEnabled) | $($result.RuleSetVersion) | $($result.RuleSetAction) | $($result.DomainsProtected) | $statusText |`n" } $mdInfo += $formatTemplate -f $tableRows } # Standard SKU profiles table (if any) if ($skippedResults.Count -gt 0) { $mdInfo += "`n### Standard SKU profiles (Skipped - bot protection not available)`n`n" $skippedTableRows = "" $skippedFormatTemplate = @' | Subscription | Profile name | SKU | Status | | :--- | :--- | :--- | :--- | {0} '@ foreach ($result in $skippedResults) { $subscriptionLink = "[$(Get-SafeMarkdown $result.SubscriptionName)]($portalSubscriptionBaseLink/$($result.SubscriptionId)/overview)" $profileLink = "[$(Get-SafeMarkdown $result.ProfileName)]($portalResourceBaseLink$($result.ProfileId)/overview)" $skippedTableRows += "| $subscriptionLink | $profileLink | $($result.SkuName) | Skipped - Standard SKU |`n" } $mdInfo += $skippedFormatTemplate -f $skippedTableRows } # Summary $mdInfo += "**Summary:**`n`n" $mdInfo += "- Total Azure Front Door Premium profiles evaluated: $($evaluationResults.Count)`n" $mdInfo += "- Profiles with bot protection enabled: $($passedItems.Count)`n" $mdInfo += "- Profiles without bot protection: $($failedItems.Count)`n" $mdInfo += "- Standard SKU profiles (skipped): $($skippedResults.Count)`n" # Replace the placeholder with detailed information $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation $params = @{ TestId = '26884' Title = 'Bot protection ruleset is enabled and assigned in Azure Front Door WAF' Status = $passed Result = $testResultMarkdown } Add-ZtTestResultDetail @params } |