Download Azure WAF V2 Blocking Logs w/PowerShell

Summary: Downloading and viewings the blocking logs for the Azure Web Application Firewall (V2) is necessary to adjust the blocking rules for the WAF. Even when the WAF is in the default “discovery” mode, there still may be some default blocking behavior.

First, the Application Gateway which is hosting the WAF needs to be enabled to send its diagnostic logs to a Log Analytics Workspace in Azure. After creating the Log Analytics Workspace, enable the diagnostic log forwarding (this example is via the Azure Portal):

Select the logs and Log Analytics Workspace.

The following PowerShell script will download logs for the past 24hrs from the WAF’s Log Analytics Workspace and export a CSV of the results.

Get-WafLogs.ps1 (example):

<#
.DESCRIPTION
    Gets WAF Logs
.EXAMPLE
    PS >> .\Get-WafLogs.ps1
.NOTES
    AUTHORS: Otto Helweg
    LASTEDIT: March 7, 2021
    VERSION: 1.0.1
    POWERSHELL: Requires version 6
    Update Execution Policy and Modules:
        Set-ExecutionPolicy Bypass -Force
    Login to Azure first:
            Logout-AzAccount
            Login-AzAccount -Subscription "<Subscription Name>"
            Select-AzSubscription -Subscription "<Subscription Name>"
    Example:
        .\Get-WafLogs.ps1 -workspaceName <Workspace Name> -workspaceRG <Resource Group Name>
#>

param($workspaceName,$workspaceRG)

if (!($workspaceName)) {
    $workspaceName = Read-Host "Workspace Name"
}

if (!($workspaceRG)) {
    $workspaceRG = Read-Host "Resource Group Name"
}

$WorkspaceID = (Get-AzOperationalInsightsWorkspace -Name $workspaceName -ResourceGroupName $workspaceRG).CustomerID
$query = 'AzureDiagnostics | where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"'

$results = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceID -Query $query -Timespan (New-TimeSpan -days 1)

# $results.results | export-csv c:\temp\LogAnalyticsLogs.csv -Delimiter "," -NoTypeInformation

$csvOutput = @()
foreach ($result in $results.Results) {
    $rule = ""
    $uri = ""

    if ($result.details_file_s) {
        $rule = (($result.details_file_s).Split("/")[1]).Split(".")[0]
    } else {
        $rule = "N/A"
    }

    $uri = "$($result.hostname_s)$($result.requestUri_s)"
    $timeStamp = Get-Date $result.TimeGenerated -Format "dd/MM/yyyy HH:mm:ss"

    # Write-Output "$timeStamp,$($result.clientIp_s),$uri,$($result.action_s),$($result.ruleSetType_s),$($result.ruleId_s),$rule,$($result.Message)"
    Write-Host "." -NoNewline

    $csvOutputItem = New-Object -Type PSObject -Property @{
        TimeStamp = $timeStamp
        ClientIP = $result.clientIp_s
        URI = $uri
        Action = $result.action_s
        RuleSet = $result.ruleSetType_s
        RuleID = $result.ruleId_s
        RuleName = $rule
        Message = $result.Message
    } | Select-Object TimeStamp,ClientIP,URI,Action,RuleSet,RuleID,RuleName,Message
    $csvOutput += $csvOutputItem
}

Write-Output ""

$csvFileName = "$((Get-Date).Year)$((Get-Date).Month.ToString('00'))$((Get-Date).Day.ToString('00'))$((Get-Date).Hour.ToString('00'))$((Get-Date).Minute.ToString('00'))$((Get-Date).Second.ToString('00'))-WAFLogs.csv"
Write-Output "Exporting to file .\$csvFileName"
$csvOutput | Export-Csv -Path .\$csvFileName -NoType

The output (formatted in Excel) shows the WAF logs based on which rule and action were applied. These logs can be used to fine tune the rules used when the WAF is in ‘blocking’ mode as well as viewing the actions taken by the WAF while in ‘discovery’ mode.

Enjoy!

Add Network Security Rules to Azure NSGs w/PowerShell

Summary: This script will add specific Network Security Group Rules to Azure Network Security Groups in an Azure Subscription. This script requires a CSV input file with the following format:

NSG Name,Rule Name,Priority,Action,Protocol,Direction,Source IP,Source Port,Destination IP,Destination Port

Note: This script does not overwrite existing Rules and will skip an NSG if that rule name or priority is already set within an NSG. In addition, this script does not check to see if any preceding rules will block the new rule.

Set-NSGs.csv (example):

nsg-vnet-otto-test-ws2-01,Allow_Test_Inbound_1,500,Allow,TCP,Inbound,10.10.10.10,8080,172.198.1.1,3389

Set-NSGs.ps1 (example):

<#
.DESCRIPTION
    Sets NSG Rules for Network Security Groups
.EXAMPLE
    PS >> .\Set-NSGs.ps1
.NOTES
    AUTHORS: Otto Helweg
    LASTEDIT: February 9, 2021
    VERSION: 1.0.0
    POWERSHELL: Requires version 6
    Update Execution Policy and Modules:
        Set-ExecutionPolicy Bypass -Force
    Login to Azure first:
            Logout-AzAccount
            Login-AzAccount -Subscription "<Azure Subscription>"
            Select-AzSubscription -Subscription "<Azure Subscription>"
    Example:
        .\Set-NSGs.ps1 -Wait -inputFile "Set-NSGs.csv"
#>

param($inputFile)

if (!($inputFile)) {
    $inputFile = "Set-NSGs.csv"
}

$csvContent = Get-Content "./$inputFile"
foreach ($item in $csvContent) {
    $duplicateRule = $false
    $nsgName,$ruleName,$priority,$access,$protocol,$direction,$sourcePrefix,$sourcePort,$destinationPrefix,$destinationPort = $item.Split(",")

    Write-Output "Working on Rule: $nsgName - $ruleName"
    $nsg = Get-AzNetworkSecurityGroup -Name $nsgName

    foreach ($rule in $nsg.SecurityRules) {
        if (($rule.Name -eq $ruleName) -or (($rule.Direction -eq $direction) -and ($rule.Priority -eq $priority))) {
            Write-Output ">> Duplicate Rule Found! Check $ruleName, $direction and $priority"
            $duplicateRule = $true
        }
    }

    if ($duplicateRule -eq $false) {
        Write-Output "> Creating new NSG Rule"

        # Add the inbound security rule.
        $nsg | Add-AzNetworkSecurityRuleConfig -Name $ruleName -Description "Added by PowerShell" -Access $access `
            -Protocol $protocol -Direction $direction -Priority $priority -SourceAddressPrefix $sourcePrefix -SourcePortRange $sourcePort `
            -DestinationAddressPrefix $destinationPrefix -DestinationPortRange $destinationPort

        # Update the NSG.
        $nsg | Set-AzNetworkSecurityGroup
    }
}

Enjoy!

View Azure WAF V2 Blocking Logs

Summary: Azure WAF Blocking Logs can be viewed via a KQL Query in the Azure Portal Log Analytics Workspace. The Application Gateway hosting the WAF must be configured to forward logs to the Log Analytics Workspace (see Diagnostic Settings for the Application Gateway in the Portal).

The following query is used to grab blocking WAF logs:

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"

Run this query in the Azure Portal.

Expanding a log entry will show the Source IP, Destination Host IP, Rule Name, and Rule Action.

These logs can be exported to a CSV file and imported into Excel for further review.

Enjoy!

Add Azure VM Tags w/PowerShell

Summary: PowerShell can easily update or add tags to all VMs within an Azure Subscription. The following will only add or update tags, not remove existing tags. This script requires a CSV input file with the following format:

VM Name,Tag:Value,Tag:Value,Tag:Value,...

This script will break if there are any commas or colons in the Tag Name or Tag Value since they are used to parse the input file. The script can be updated to adjust delimiter handling.

Set-VMTags.csv (example):

xbogus,BuiltBy:otto.helweg@cloudrobots.net,Application:Test,AppOwner:otto.helweg@cloudrobots.net,Account:123456
otto-test-linux,Owner:otto,needed-until-date:2020-12-31,environment:test
otto-test-linux-2,Owner:otto,needed-until-date:2020-12-31,environment:test
otto-test-win,Owner:otto,needed-until-date:2020-12-31,environment:test
otto-dev-win10,Owner:otto,needed-until-date:2021-12-31,environment:dev
Otto-MyWindows,Owner:otto,needed-until-date:2021-12-31,environment:dev

Tags are then applied to the VM instance, and the associated Disks and NICs (associated Public IPs (PIPs) are not included.

Set-VMTags.ps1 (example):

<#
.DESCRIPTION
    Set tags for all VMs in a subscription
.EXAMPLE
    PS >> .\Set-VMMTags.ps1
.NOTES
    AUTHORS: Otto Helweg
    LASTEDIT:September 2, 2020
    VERSION: 1.0.3
    POWERSHELL: Requires version 6
    Update Execution Policy and Modules:
        Set-ExecutionPolicy Bypass -Force
    Login to Azure first:
            Logout-AzAccount
            Login-AzAccount -Subscription "<Azure Subscription>"
            Select-AzSubscription -Subscription "<Azure Subscription>"
    Example:
        .\Set-VMTags.ps1 -Wait -inputFile "Set-VMTags.csv"
#>

param($inputFile)

if (!($inputFile)) {
    $inputFile = "Set-VMTags.csv"
}

$csvContent = Get-Content "./$inputFile"
$vmList = @{}
foreach ($item in $csvContent) {
    $tags = @{}
    $vmName,$vmTags = $item.Split(",")
    if ($vmName -and $vmTags) {
        $vmList[$vmName] = $vmTags
        foreach ($tag in $vmTags) {
            $tagData = $tag.Split(":")
            $tags[$tagData[0]] = $tagData[1]
        }

        $vmAzure = Get-AzVM -Name "$vmName"

        if ($vmAzure) {
            Write-Output "$vmName VM updating Tags"
            Update-AzTag -ResourceId $vmAzure.Id -Operation Merge -Tag $tags
            foreach ($nic in $vmAzure.NetworkProfile.NetworkInterfaces) {
                Write-Output "> $vmName NIC updating Tags"
                Update-AzTag -ResourceId $nic.Id -Operation Merge -Tag $tags
            }
            if ($vmAzure.StorageProfile.OsDisk.ManagedDisk.Id) {
                Write-Output "> $vmName Disk $($vmAzure.StorageProfile.OsDisk.Name) updating Tags"
                Update-AzTag -ResourceId $vmAzure.StorageProfile.OsDisk.ManagedDisk.Id -Operation Merge -Tag $tags
            }
            foreach ($disk in $vmAzure.StorageProfile.DataDisks) {
                Write-Output "> $vmName Disk $($disk.Name) updating Tags"
                $azResource = Get-AzResource -Name "$($disk.Name)"
                Update-AzTag -ResourceId $azResource.Id -Operation Merge -Tag $tags
            }

            if ($Args -contains "-Wait") {
                Read-Host "Press Enter to continue"
            }
        } else {
            Write-Output "$vmName VM not found"
        }
    } else {
        Write-Output "Malformed tags"
    }
}

Enjoy!