Azure Firewall (Public Preview) Automation – Part 3

In  the previous article, we played a bit more advanced with PowerShell to pull over 1,000 malicious hosts from MDL (Malware Domain List) and then created Azure Firewall application rules accordingly. To get the list being up-to-date state, you may run the list in a periodical schedule or have a virtual machine with scheduler (e.g. Task Scheduler) to run your script. Another approach to be considered is Azure Automation to automate firewall rule creation and update.

In this article, we will look into deploying the PowerShell script in an automation runbook and schedule it to continuously maintain  firewall application rule.

Disclaimer: this article is not going to introduce Azure Automation and its features. I’d highly recommend you to ramp up Azure Automation from Microsoft Docs website here.

Automation Account Preparation

To facilitate access control when automating against Azure resources, you should use Azure Run As account. When choosing this account type, Azure helps automatically create a service principal and assign Contributor role to your target subscription by default.

Azure Automation account can be created in Azure Portal or PowerShell script provided by Microsoft here.

Module Installation

Azure Automation provides several runbook types. Depending on the runbook, the module required may vary. In this article PowerShell runbook is going to be used. That said, if you must be aware of Azure PowerShell modules which support your automation script.

The following modules must be installed or updated:

  • AzureRm.Profile 5.3.3 or newer.
  • AzureRm.Network 6.4.0-preview

By default, PowerShell modules for Azure are installed during automation account creation. However, Azure doesn’t update module to the latest version by default. AzureRm.Network module is not installed in the initial module list.

For AzureRm.Profile, you can easily update this module to the latest one by clicking Update Azure Modules from the top bar. You can also go to Module Gallery and import AzureRm.Profile into your automation account’s module list. This way doesn’t work with AzureRm.Network because the only module supporting Azure Firewall is 6.4.0-preview while this way installs the latest one. In this case, you can use New-AzureRmAutomationModule  cmdlet to install a specified module version.

$automationAccountName = "security-automation"
$rgName = "security-automation-rg"
$moduleName = "AzureRM.Network"
$moduleVersion = "6.4.0-preview"
$moduleLink = "https://www.powershellgallery.com/api/v2/package/$($moduleName)/$($moduleVersion)/"


New-AzureRmAutomationModule -Name $moduleName `
                            -AutomationAccountName $automationAccountName `
                            -ResourceGroupName $rgName `
                            -ContentLink $moduleLink

If you would like to update AzureRm.Profile module using PowerShell script, you can use Set-AzureRmAutomationModule  cmdlet.

$automationAccountName = "security-automation"
$rgName = "security-automation-rg"
$moduleName = "AzureRM.profile"
$moduleVersion = "5.3.3"
$moduleLink = "https://www.powershellgallery.com/api/v2/package/$($moduleName)/$($moduleVersion)/"


Set-AzureRmAutomationModule -Name $moduleName `
                            -AutomationAccountName $automationAccountName `
                            -ResourceGroupName $rgName `
                            -ContentLink $moduleLink

After updating modules, it is better to verify in the AzureRm.Network module to make sure all Azure Firewall cmdlets are available and ready for your runbook.

Even the indicated version you see is 6.4.0, the actual version is 6.4.0-preview.

If you install 6.4.0 Azure Firewall cmdlets won’t be available for use.

Runbook Preparation

Creating a PowerShell runbook is the next step in which your PowerShell script is hosted and run. The new runbook can be created either in Azure Portal or PowerShell. Run the following script to quickly create a new PowerShell runbook:

$automationAccountName = "security-automation"
$rgName = "security-automation-rg"
$runbookName = "az-firewall-mdl"

New-AzureRmAutomationRunbook -AutomationAccountName $automationAccountName `
                             -Name $runbookName `
                             -ResourceGroupName $rgName `
                             -Type PowerShell

When runbook is ready, you can import your PowerShell script or manually copy PowerShell code into the runbook. The script in the previous article is used after the context is retrieved. In the runbook, you also need to create a context for your automation account prior to getting access to subscription and work with Azure Firewall resources. Simply add the following block of code into the script

$connectionName = "AzureRunAsConnection"
try {
    # Get the connection "AzureRunAsConnection "
    $servicePrincipalConnection = Get-AutomationConnection -Name $connectionName         

    "Logging in to Azure..."
    Add-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection.TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
}
catch {
    if (!$servicePrincipalConnection) {
        $ErrorMessage = "Connection $connectionName not found."
        throw $ErrorMessage
    }
    else {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}

The script uses the service principal connection which is automatically provisioned during your automation account creation (with Run As type). Using Run As account, it is much easier for an authorization against Azure AD.

One more thing to be updated is the location where the list is stored. If you are not going to maintain it in your blob storage, you can store it locally in temp folder

$rule_file_path = Join-Path -Path (Join-Path -Path $env:TMP -ChildPath 'fw') -ChildPath 'blacklist'

Below is the full script:

$fwName = "fw01"
$rgName = 'pentest-rg'
$last_updated = Get-Date -Format yyyyMMddThhmmssZ
$rule_file_path = Join-Path -Path (Join-Path -Path $env:TMP -ChildPath 'fw') -ChildPath 'blacklist'
$cleaned_file_name = "$rule_file_path" + "$last_updated" + ".txt"
$mdlList = "http://www.malwaredomainlist.com/hostslist/hosts.txt "

Write-Output $rule_file_path
Write-Output $cleaned_file_name


$connectionName = "AzureRunAsConnection"
try {
    # Get the connection "AzureRunAsConnection "
    $servicePrincipalConnection = Get-AutomationConnection -Name $connectionName         

    "Logging in to Azure..."
    Add-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection.TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
}
catch {
    if (!$servicePrincipalConnection) {
        $ErrorMessage = "Connection $connectionName not found."
        throw $ErrorMessage
    }
    else {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}

$azFirewall = Get-AzureRmFirewall -Name $fwName -ResourceGroupName $rgName
if (!$azFirewall) {
    throw "No target Azure Firewall resource is not found"
}
else {
    Write-Output $azFirewall.Name
}

# Download and process host list
Invoke-WebRequest $mdlList -OutFile $cleaned_file_name
$rawList = Get-Content -path $cleaned_file_name | Select-Object -Skip 6 
$cleanedList = $rawList

Write-Output $cleanedList

# Create multiple rules
$ruleset = @()
Foreach ($hostList in $cleanedList) { 
    $trimmedHost = ($hostList.Trim("127.0.0.1")).Trim()
    ForEach ($blockUrl in $trimmedHost) {
        $ruleset += New-AzureRmFirewallApplicationRule -Name $blockUrl `
            -SourceAddress * `
            -Protocol Http:80, Https:443 `
            -TargetFqdn $blockUrl -Verbose
    }
}

# Create a new application rule collection
$ruleCollection = New-AzureRmFirewallApplicationRuleCollection -Name MDLHost `
    -Priority 300 `
    -Rule $ruleset  `
    -ActionType "Deny"

$azFirewall.ApplicationRuleCollections = $ruleCollection
Set-AzureRmFirewall -AzureFirewall $azFirewall

You may have protected variable to mask your variables in the script, e.g. firewall resource name, subscription ID or what is supposed to be hidden in the script.

Once the script is ready, run the following to import your PowerShell script to the newly created runbook.

$automationAccountName = "security-automation"
$rgName = "security-automation-rg"
$runbookName = "az-firewall-mdl"
$scriptLocation = "D:\Dev\azFirewall\az-firewall-mdl.ps1"


Import-AzureRMAutomationRunbook -Name $runbookName `
                                -Path $scriptLocation `
                                -ResourceGroupName $RGName `
                                -AutomationAccountName $automationAccountName `
                                -Type PowerShell

Now you can test the runbook by using Test Pane. You can also go to Activity Log in your Azure Firewall resource to verify.

Performance Caveat

Since the article is written to show the demonstration of using Automation Runbook to interact with Azure Firewall, there is no optimal performance or practical function-driven approach in writing PowerShell. Moreover, the total rules to be created is over 1,000 which would block the runbook execution. Per my observation, it takes pretty long to complete the script from a runbook. There is a couple of approaches to reduce execution time:

  • Deploy a hybrid worker: while Azure Automation on Azure provides limited computing resource to run your script, Hybrid Worker is an option for better performance with higher computing resource. You can deploy a virtual machine whose memory is 8GB ram to run the script (like the local one I’m using which looks fast). The disadvantage of this approach is cost required to run your virtual machine 24/7. Otherwise you also use another runbook to schedule turn-on/turn-off the virtual machine. Another disadvantage is the network security control in case you’d like to restrict.
  • Decouple runbook: in this approach you would have multiple runbooks running. One is pulling the list and writing to a blob storage while another one is reading to that blob and creating rules accordingly. The runbook can be called by using Start-AzureRmAutomationRunbook  cmdlet.

Conclusion

This article focuses on using Azure Automation runbook to automate Azure Firewall application rules. Hence, the script provided in the article looks very dirty and not a clean one. I’m sure it is missing exception handling, function wrapper, naming convention or other best practices of PowerShell coding. You’d be the one loving to clean and make the code look fancy.

In the next article, let’s explore a multi-automation service pattern in which Azure Automation is used for handling MDL while Azure Function is triggered to deploy an updated application rule. Stay tuned!

This entry was posted in Security Automation and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *