tests/Test-Assessment.26888.ps1

<#
.SYNOPSIS
    Validates that diagnostic logging is enabled for Application Gateway WAF.
 
.DESCRIPTION
    This test evaluates diagnostic settings for Azure Application Gateway resources with WAF SKU
    to ensure log categories are enabled with a valid destination configured (Log Analytics,
    Storage Account, or Event Hub).
 
.NOTES
    Test ID: 26888
    Category: Azure Network Security
    Required APIs: Azure Management REST API (subscriptions, application gateways, diagnostic settings)
#>


function Test-Assessment-26888 {

    [ZtTest(
        Category = 'Azure Network Security',
        ImplementationCost = 'Low',
        MinimumLicense = ('Azure_Application_Gateway_WAF'),
        Pillar = 'Network',
        RiskLevel = 'High',
        SfiPillar = 'Monitor and detect cyberthreats',
        TenantType = ('Workforce'),
        TestId = 26888,
        Title = 'Diagnostic logging is enabled in Application Gateway WAF',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection

    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
    $activity = 'Evaluating Application Gateway 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
    }

    # 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 Application Gateways with WAF SKU using Azure Resource Graph
    Write-ZtProgress -Activity $activity -Status 'Querying Application Gateways via Resource Graph'

    $argQuery = @"
resources
| where type =~ 'microsoft.network/applicationgateways'
| where properties.provisioningState =~ 'Succeeded'
| where properties.sku.tier in~ ('WAF', 'WAF_v2')
| join kind=leftouter (
    resourcecontainers
    | where type =~ 'microsoft.resources/subscriptions'
    | project subscriptionName=name, subscriptionId
) on subscriptionId
| project
    GatewayName=name,
    GatewayId=id,
    Location=location,
    SkuTier=tostring(properties.sku.tier),
    SubscriptionId=subscriptionId,
    SubscriptionName=subscriptionName
"@


    $allAppGateways = @()
    try {
        $allAppGateways = @(Invoke-ZtAzureResourceGraphRequest -Query $argQuery)
        Write-PSFMessage "ARG Query returned $($allAppGateways.Count) Application Gateway WAF(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 Application Gateway WAF resources exist
    if ($allAppGateways.Count -eq 0) {
        Write-PSFMessage 'No Application Gateway WAF resources found.' -Tag Test -Level VeryVerbose
        Add-ZtTestResultDetail -SkippedBecause NotApplicable
        return
    }

    # Q3: Get diagnostic settings for each Application Gateway WAF
    Write-ZtProgress -Activity $activity -Status 'Querying diagnostic settings'

    $evaluationResults = @()

    foreach ($appGateway in $allAppGateways) {
        $appGatewayId = $appGateway.GatewayId
        $appGatewayName = $appGateway.GatewayName
        $appGatewayLocation = $appGateway.Location
        $appGatewaySkuTier = $appGateway.SkuTier

        # Q3: Query diagnostic settings using Invoke-ZtAzureRequest
        $diagPath = $appGatewayId + '/providers/Microsoft.Insights/diagnosticSettings?api-version=2021-05-01-preview'

        $diagSettings = @()
        try {
            $diagSettings = @(Invoke-ZtAzureRequest -Path $diagPath)
        }
        catch {
            Write-PSFMessage "Error querying diagnostic settings for $appGatewayName : $_" -Level Warning
        }

        # Evaluate diagnostic settings
        $hasValidDiagSetting = $false
        $allDestinationTypes = @()
        $enabledCategories = @()
        $diagSettingNames = @()

        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) {
                # Determine destination type
                $destTypes = @()
                if ($workspaceId) { $destTypes += 'Log Analytics' }
                if ($storageAccountId) { $destTypes += 'Storage' }
                if ($eventHubAuthRuleId) { $destTypes += 'Event Hub' }

                # Collect all enabled log categories from this setting
                $logs = $setting.properties.logs
                $settingEnabledCategories = @()
                foreach ($log in $logs) {
                    if ($log.enabled) {
                        # Handle both category and categoryGroup (per spec)
                        $categoryName = if ($log.category) { $log.category } else { $log.categoryGroup }
                        if ($categoryName) {
                            $settingEnabledCategories += $categoryName
                        }
                    }
                }

                # If this setting has destination AND enabled logs, it's valid
                if ($settingEnabledCategories.Count -gt 0) {
                    $hasValidDiagSetting = $true
                    $diagSettingNames += $setting.name
                    $allDestinationTypes += $destTypes
                    $enabledCategories += $settingEnabledCategories
                }
            }
        }

        # Deduplicate enabled categories and destination types (multiple settings may have same values)
        $enabledCategories = $enabledCategories | Select-Object -Unique
        $destinationType = if ($allDestinationTypes.Count -gt 0) { ($allDestinationTypes | Select-Object -Unique) -join ', ' } else { 'None' }

        $status = if ($hasValidDiagSetting) { 'Pass' } else { 'Fail' }

        $evaluationResults += [PSCustomObject]@{
            SubscriptionId         = $appGateway.SubscriptionId
            SubscriptionName       = $appGateway.SubscriptionName
            GatewayName            = $appGatewayName
            GatewayId              = $appGatewayId
            Location               = $appGatewayLocation
            SkuTier                = $appGatewaySkuTier
            DiagnosticSettingCount = $diagSettings.Count
            DiagnosticSettingName  = ($diagSettingNames | Select-Object -Unique) -join ', '
            DestinationType        = $destinationType
            EnabledCategories      = $enabledCategories -join ', '
            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 Application Gateway WAF with active log collection configured.`n`n%TestResult%"
    }
    else {
        $testResultMarkdown = "❌ Diagnostic logging is not enabled for Application Gateway WAF, preventing security monitoring and threat detection.`n`n%TestResult%"
    }

    #endregion Assessment Logic

    #region Report Generation

    # Portal link variables
    $portalAppGatewayBrowseLink = 'https://portal.azure.com/#browse/Microsoft.Network%2FapplicationGateways'
    $portalSubscriptionBaseLink = 'https://portal.azure.com/#resource/subscriptions'
    $portalResourceBaseLink = 'https://portal.azure.com/#resource'

    $mdInfo = "`n## [Application Gateway WAF diagnostic logging status]($portalAppGatewayBrowseLink)`n`n"

    # Application Gateway WAF Status table
    if ($evaluationResults.Count -gt 0) {
        $tableRows = ""
        $formatTemplate = @'
| Subscription | Gateway name | Location | SKU tier | Diagnostic settings count | Destination configured | Enabled log categories | Status |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
{0}
 
'@


        # Limit display to first 5 items if there are many gateways
        $maxItemsToDisplay = 5
        $displayResults = $evaluationResults
        $hasMoreItems = $false
        if ($evaluationResults.Count -gt $maxItemsToDisplay) {
            $displayResults = $evaluationResults | Select-Object -First $maxItemsToDisplay
            $hasMoreItems = $true
        }

        foreach ($result in $displayResults) {
            $subscriptionLink = "[$(Get-SafeMarkdown $result.SubscriptionName)]($portalSubscriptionBaseLink/$($result.SubscriptionId)/overview)"
            $gatewayLink = "[$(Get-SafeMarkdown $result.GatewayName)]($portalResourceBaseLink$($result.GatewayId)/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 | $gatewayLink | $($result.Location) | $($result.SkuTier) | $diagCount | $destConfigured | $enabledCategories | $statusText |`n"
        }

        # Add note if more items exist
        if ($hasMoreItems) {
            $remainingCount = $evaluationResults.Count - $maxItemsToDisplay
            $tableRows += "`n... and $remainingCount more. [View all Application Gateways in the portal]($portalAppGatewayBrowseLink)`n"
        }

        $mdInfo += $formatTemplate -f $tableRows
    }

    # Summary
    $mdInfo += "**Summary:**`n`n"
    $mdInfo += "- Total Application Gateways with WAF evaluated: $($evaluationResults.Count)`n"
    $mdInfo += "- Gateways with diagnostic logging enabled: $($passedItems.Count)`n"
    $mdInfo += "- Gateways without diagnostic logging: $($failedItems.Count)`n"

    # Replace the placeholder with detailed information
    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo

    #endregion Report Generation

    $params = @{
        TestId = '26888'
        Title  = 'Diagnostic logging is enabled in Application Gateway WAF'
        Status = $passed
        Result = $testResultMarkdown
    }

    Add-ZtTestResultDetail @params
}