tests/Test-Assessment.26889.ps1
|
<#
.SYNOPSIS Validates that diagnostic logging is enabled for Azure Front Door WAF. .DESCRIPTION This test evaluates diagnostic settings for Azure Front Door profiles to ensure WAF log categories are enabled with a valid destination configured (Log Analytics, Storage Account, or Event Hub). .NOTES Test ID: 26889 Category: Azure Network Security Required APIs: Azure Management REST API (subscriptions, profiles, WAF policies, diagnostic settings) #> function Test-Assessment-26889 { [ZtTest( Category = 'Azure Network Security', ImplementationCost = 'Low', MinimumLicense = ('Azure_FrontDoor_Standard', 'Azure_FrontDoor_Premium'), Pillar = 'Network', RiskLevel = 'High', SfiPillar = 'Monitor and detect cyberthreats', TenantType = ('Workforce'), TestId = 26889, Title = 'Diagnostic logging is enabled in Azure Front Door WAF', UserImpact = 'Low' )] [CmdletBinding()] param() # Required log categories for comprehensive Azure Front Door WAF monitoring $REQUIRED_LOG_CATEGORIES = @( 'FrontDoorAccessLog', 'FrontDoorWebApplicationFirewallLog', 'FrontDoorHealthProbeLog' ) # WAF log category to check for pass/fail criteria (per spec) $WAF_LOG_CATEGORY = 'FrontDoorWebApplicationFirewallLog' # Valid Azure Front Door SKUs $VALID_FRONT_DOOR_SKUS = @('Standard_AzureFrontDoor', 'Premium_AzureFrontDoor') #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Evaluating Azure Front Door WAF diagnostic logging 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 } # Q1: List all subscriptions Write-ZtProgress -Activity $activity -Status 'Querying subscriptions' $subscriptionsPath = '/subscriptions?api-version=2022-12-01' $subscriptions = $null try { $result = Invoke-AzRestMethod -Path $subscriptionsPath -ErrorAction Stop if ($result.StatusCode -eq 403) { Write-PSFMessage 'The signed in user does not have access to list subscriptions.' -Level Verbose Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } if ($result.StatusCode -ge 400) { throw "Subscriptions request failed with status code $($result.StatusCode)" } # Azure REST list APIs are paginated. # Handling nextLink is required to avoid missing subscriptions. $allSubscriptions = @() $subscriptionsJson = $result.Content | ConvertFrom-Json if ($subscriptionsJson.value) { $allSubscriptions += $subscriptionsJson.value } $nextLink = $subscriptionsJson.nextLink try { while ($nextLink) { $result = Invoke-AzRestMethod -Uri $nextLink -Method GET $subscriptionsJson = $result.Content | ConvertFrom-Json if ($subscriptionsJson.value) { $allSubscriptions += $subscriptionsJson.value } $nextLink = $subscriptionsJson.nextLink } } catch { Write-PSFMessage "Failed to retrieve next page of subscriptions: $_. Continuing with collected data." -Level Warning } $subscriptions = $allSubscriptions | Where-Object { $_.state -eq 'Enabled' } } catch { Write-PSFMessage "Failed to enumerate Azure subscriptions while evaluating Front Door WAF diagnostic logging: $_" -Level Error throw } if ($null -eq $subscriptions -or $subscriptions.Count -eq 0) { Write-PSFMessage 'No enabled subscriptions found.' -Level Warning Add-ZtTestResultDetail -SkippedBecause NotSupported return } # Collect Front Door resources and WAF policies across all subscriptions $allAfdResources = @() $allWafPolicies = @() $frontDoorQuerySuccess = $false $wafQuerySuccess = $false foreach ($subscription in $subscriptions) { $subscriptionId = $subscription.subscriptionId # Q2: List Azure Front Door resources Write-ZtProgress -Activity $activity -Status "Querying Front Door resources in subscription $subscriptionId" $afdListPath = "/subscriptions/$subscriptionId/providers/Microsoft.Cdn/profiles?api-version=2024-02-01" try { $afdListResult = Invoke-AzRestMethod -Path $afdListPath -ErrorAction Stop if ($afdListResult.StatusCode -lt 400) { $frontDoorQuerySuccess = $true # Azure REST list APIs are paginated. # Handling nextLink is required to avoid missing Front Door profiles. $allCdnResources = @() $cdnJson = $afdListResult.Content | ConvertFrom-Json if ($cdnJson.value) { $allCdnResources += $cdnJson.value } $nextLink = $cdnJson.nextLink try { while ($nextLink) { $afdListResult = Invoke-AzRestMethod -Uri $nextLink -Method GET $cdnJson = $afdListResult.Content | ConvertFrom-Json if ($cdnJson.value) { $allCdnResources += $cdnJson.value } $nextLink = $cdnJson.nextLink } } catch { Write-PSFMessage "Failed to retrieve next page of Front Door profiles for subscription '$subscriptionId': $_. Continuing with collected data." -Level Warning } # Filter for Standard/Premium Azure Front Door SKUs $afdResources = $allCdnResources | Where-Object { $_.sku.name -in $VALID_FRONT_DOOR_SKUS } foreach ($afdResource in $afdResources) { $allAfdResources += [PSCustomObject]@{ SubscriptionId = $subscriptionId SubscriptionName = $subscription.displayName FrontDoor = $afdResource } } } } catch { Write-PSFMessage "Error querying Front Door resources in subscription $subscriptionId : $_" -Level Warning } # Q3: List WAF policies Write-ZtProgress -Activity $activity -Status "Querying WAF policies in subscription $subscriptionId" $wafPath = "/subscriptions/$subscriptionId/providers/Microsoft.Network/FrontDoorWebApplicationFirewallPolicies?api-version=2024-02-01" try { $wafResult = Invoke-AzRestMethod -Path $wafPath -ErrorAction Stop if ($wafResult.StatusCode -lt 400) { $wafQuerySuccess = $true # Azure REST list APIs are paginated. # Handling nextLink is required to avoid missing WAF policies. $allWafPoliciesInSub = @() $wafJson = $wafResult.Content | ConvertFrom-Json if ($wafJson.value) { $allWafPoliciesInSub += $wafJson.value } $nextLink = $wafJson.nextLink try { while ($nextLink) { $wafResult = Invoke-AzRestMethod -Uri $nextLink -Method GET $wafJson = $wafResult.Content | ConvertFrom-Json if ($wafJson.value) { $allWafPoliciesInSub += $wafJson.value } $nextLink = $wafJson.nextLink } } catch { Write-PSFMessage "Failed to retrieve next page of WAF policies for subscription '$subscriptionId': $_. Continuing with collected data." -Level Warning } foreach ($policy in $allWafPoliciesInSub) { $allWafPolicies += [PSCustomObject]@{ SubscriptionId = $subscriptionId Policy = $policy } } } } catch { Write-PSFMessage "Error querying WAF policies in subscription $subscriptionId : $_" -Level Warning } } # Check if any Front Door resources exist if ($allAfdResources.Count -eq 0) { if (-not $frontDoorQuerySuccess) { Write-PSFMessage 'Unable to query Front Door resources in any subscription due to access restrictions.' -Level Warning Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } Write-PSFMessage 'No Azure Front Door Standard/Premium resources found.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotLicensedOrNotApplicable return } # Check if WAF policies could be queried if ($allWafPolicies.Count -eq 0 -and -not $wafQuerySuccess) { Write-PSFMessage 'Unable to query WAF policies in any subscription due to access restrictions.' -Level Warning Add-ZtTestResultDetail -SkippedBecause NoAzureAccess return } # Filter profiles to only those with associated WAF policies (per spec step 3) # Use Q3's securityPolicyLinks to find WAF associations $profilesWithWaf = @() foreach ($fdItem in $allAfdResources) { $frontDoorId = $fdItem.FrontDoor.id # Check if any WAF policy has securityPolicyLinks pointing to this Front Door $matchedWafPolicy = $allWafPolicies | Where-Object { $_.Policy.properties.securityPolicyLinks | Where-Object { $_.id -match [regex]::Escape($frontDoorId) } } if ($matchedWafPolicy) { $profilesWithWaf += [PSCustomObject]@{ SubscriptionId = $fdItem.SubscriptionId SubscriptionName = $fdItem.SubscriptionName FrontDoor = $fdItem.FrontDoor WafPolicyName = ($matchedWafPolicy.Policy.name | Select-Object -Unique) -join ', ' } } } # Skip if no Azure Front Door profiles with WAF exist (per spec step 6) if ($profilesWithWaf.Count -eq 0) { Write-PSFMessage 'No Azure Front Door profiles with WAF found.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotLicensedOrNotApplicable return } # Q4: Get diagnostic settings for each Front Door resource Write-ZtProgress -Activity $activity -Status 'Querying diagnostic settings' $evaluationResults = @() # Iterate only over profiles with WAF (filtered above using Q3 securityPolicyLinks) foreach ($fdItem in $profilesWithWaf) { $frontDoor = $fdItem.FrontDoor $frontDoorId = $frontDoor.id $frontDoorName = $frontDoor.name $wafPolicyName = $fdItem.WafPolicyName # WAF policy name from Q3 securityPolicyLinks # Q4: Query diagnostic settings $diagPath = $frontDoorId + '/providers/Microsoft.Insights/diagnosticSettings?api-version=2021-05-01-preview' $diagSettings = @() try { $diagResult = Invoke-AzRestMethod -Path $diagPath -ErrorAction Stop if ($diagResult.StatusCode -lt 400) { $diagSettings = ($diagResult.Content | ConvertFrom-Json).value } } catch { Write-PSFMessage "Error querying diagnostic settings for $frontDoorName : $_" -Level Warning } # Fallback: If WAF policy name not found via Q3 securityPolicyLinks, query security policies endpoint if (-not $wafPolicyName -or $wafPolicyName -eq 'None') { $securityPoliciesPath = $frontDoorId + '/securityPolicies?api-version=2024-02-01' try { $secPolicyResult = Invoke-AzRestMethod -Path $securityPoliciesPath -ErrorAction Stop if ($secPolicyResult.StatusCode -lt 400) { $securityPolicies = ($secPolicyResult.Content | ConvertFrom-Json).value # Get WAF policy references from security policies $wafPolicyIds = $securityPolicies | ForEach-Object { $_.properties.parameters.wafPolicy.id } | Where-Object { $_ } if ($wafPolicyIds) { # Extract WAF policy names and join if multiple $wafPolicyNames = $wafPolicyIds | ForEach-Object { ($_ -split '/')[-1] } $wafPolicyName = ($wafPolicyNames | Select-Object -Unique) -join ', ' } } } catch { Write-PSFMessage "Error querying security policies for $frontDoorName : $_" -Level Warning } } # Evaluate diagnostic settings $hasValidDiagSetting = $false $destinationType = 'None' $enabledCategories = @() $diagSettingName = 'None' foreach ($setting in $diagSettings) { $workspaceId = $setting.properties.workspaceId $storageAccountId = $setting.properties.storageAccountId $eventHubAuthRuleId = $setting.properties.eventHubAuthorizationRuleId # Check if destination is configured $hasDestination = $workspaceId -or $storageAccountId -or $eventHubAuthRuleId if ($hasDestination) { $hasValidDiagSetting = $true $diagSettingName = $setting.name # Determine destination type $destTypes = @() if ($workspaceId) { $destTypes += 'Log Analytics' } if ($storageAccountId) { $destTypes += 'Storage' } if ($eventHubAuthRuleId) { $destTypes += 'Event Hub' } $destinationType = $destTypes -join ', ' # Collect all enabled log categories $logs = $setting.properties.logs foreach ($log in $logs) { if ($log.enabled) { $enabledCategories += $log.category } } } } # Check which required log categories are enabled and if WAF log is enabled for pass criteria $enabledCategories = $enabledCategories | Select-Object -Unique $missingRequiredCategories = $REQUIRED_LOG_CATEGORIES | Where-Object { $_ -notin $enabledCategories } $wafLogEnabled = $WAF_LOG_CATEGORY -in $enabledCategories $status = if ($hasValidDiagSetting -and $wafLogEnabled) { 'Pass' } else { 'Fail' } $evaluationResults += [PSCustomObject]@{ SubscriptionId = $fdItem.SubscriptionId SubscriptionName = $fdItem.SubscriptionName FrontDoorName = $frontDoorName FrontDoorId = $frontDoorId Sku = $frontDoor.sku.name WafPolicy = $wafPolicyName DiagnosticSettingCount = $diagSettings.Count DiagnosticSettingName = $diagSettingName DestinationType = $destinationType EnabledCategories = $enabledCategories -join ', ' MissingRequiredCategories = $missingRequiredCategories -join ', ' WafLogEnabled = $wafLogEnabled Status = $status } } #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 = "✅ Diagnostic logging is enabled for Azure Front Door WAF with active log collection configured.`n`n%TestResult%" } else { $testResultMarkdown = "❌ Diagnostic logging is not enabled for Azure Front Door WAF, preventing security monitoring and threat detection at the edge.`n`n%TestResult%" } #endregion Assessment Logic #region Report Generation $mdInfo = "`n## [Azure Front Door WAF diagnostic logging status](https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.Cdn%2Fprofiles)`n`n" # Front Door Status table if ($evaluationResults.Count -gt 0) { $tableRows = "" $formatTemplate = @' | Subscription | Profile name | SKU | WAF policy | Diagnostic settings count | Destination configured | Enabled log categories | Status | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | {0} '@ foreach ($result in $evaluationResults) { $subscriptionLink = "[$(Get-SafeMarkdown $result.SubscriptionName)](https://portal.azure.com/#resource/subscriptions/$($result.SubscriptionId)/overview)" $profileLink = "[$(Get-SafeMarkdown $result.FrontDoorName)](https://portal.azure.com/#resource$($result.FrontDoorId)/diagnostics)" $diagCount = $result.DiagnosticSettingCount $destConfigured = if ($result.DestinationType -eq 'None') { 'No' } else { 'Yes' } $enabledCategories = if ($result.DiagnosticSettingCount -eq 0) { 'No diagnostic settings' } elseif ($result.EnabledCategories) { $result.EnabledCategories } else { 'None' } $statusText = if ($result.Status -eq 'Pass') { '✅ Pass' } else { '❌ Fail' } $tableRows += "| $subscriptionLink | $profileLink | $($result.Sku) | $(Get-SafeMarkdown $result.WafPolicy) | $diagCount | $destConfigured | $enabledCategories | $statusText |`n" } $mdInfo += $formatTemplate -f $tableRows } # Summary $mdInfo += "**Summary:**`n`n" $mdInfo += "- Total Azure Front Door profiles with WAF evaluated: $($evaluationResults.Count)`n" $mdInfo += "- Profiles with diagnostic logging enabled: $($passedItems.Count)`n" $mdInfo += "- Profiles without diagnostic logging: $($failedItems.Count)`n" # Replace the placeholder with detailed information $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation $params = @{ TestId = '26889' Title = 'Diagnostic logging is enabled in Azure Front Door WAF' Status = $passed Result = $testResultMarkdown } Add-ZtTestResultDetail @params } |