Using PowerShell, Azure Automation, and OMS – Part III
It’s been a long road, but we’re almost there! A couple of weeks ago we looked at how we can create an Azure Automation Account and add our own custom modules to the solution to be used in Azure Automation. Last week, we took a deeper dive into configuring a runbook to take in webhook data from an alert using Microsoft’s Operations Management Suite. Then we looked into the data itself to see how we can leverage it against our runbook to fix problems for us on the fly.
This week, we’re going to modify an existing function to use that webhook data directly.
Building on Webhook Data
We could actually build our logic directly into the runbook to parse the webhook data and then pass the formatted information to our function that we’ve made available in Azure. But I prefer to keep my runbooks as simple as possible and do the heavy lifting in my function. This makes the runbook look a little bit cleaner, and allows me to minimize my code management a little more. Also, Azure Automation Runbooks, as of this writing, don’t play nicely with parameter sets in them, so I might as well pass my data along to a command that does.
Originally, I had built a one-liner that allowed me to create an NSG rule on the fly to block and incoming traffic from a specific IPAddress. It was a fairly simple command. But today, we’re going to make it a little more robust, and give it the ability to use webhook data. Here’s my original code:
Function Set-AzureRmNSGMaliciousRule { [cmdletbinding()] Param( [Parameter(Mandatory=$true)][string]$ComputerName, [Parameter(Mandatory=$true)][string]$IPAddress ) $ResGroup = (Get-AzureRmResource).where({$PSItem.Name -eq $Sys}) $VM = Get-AzureRmVM -ResourceGroupName $ResGroup.ResourceGroupName -Name $Sys $VmNsg = (Get-AzureRmNetworkSecurityGroup -ResourceGroupName $VM.ResourceGroupName).where({$PSItem.NetworkInterfaces.Id -eq $VM.NetworkProfile.NetworkInterfaces.Id}) $Priority = ($VmNsg.SecurityRules) | Where-Object -Property Priority -LT 200 | Select-Object -Last 1 If ($Priority -eq $null){ $Pri = 100 } Else { $Pri = ($Priority + 1) } $Name = ('BlockedIP_' + $IPAddress) $NSGArgs = @{ Name = $Name Description = ('Malicious traffic from ' + $IPAddress) Protocol = '*' SourcePortRange = '*' DestinationPortRange = '*' SourceAddressPrefix = $IPAddress DestinationAddressPrefix = '*' Access = 'Deny' Direction = 'Inbound' Priority = $Pri } $VmNsg | Add-AzureRmNetworkSecurityRuleConfig @NSGArgs | Set-AzureRmNetworkSecurityGroup } I want to keep my mandatory parameters for my original one-liner solution in-case I need to do something tactically. So we’ll go ahead and split the parameters for on-prem vs. webhook into different parameter sets. As webhook data is formatted as a JSON object, we’ll need to specify the data type for the WebhookData parameter as object.
Param( [Parameter(ParameterSetName='ConsoleInput')][string]$ComputerName, [Parameter(ParameterSetName='ConsoleInput')][string]$MaliciousIP, [Parameter(ParameterSetName='WebhookInput")][object]$WebhookData ) Now, we’re going to add some logic to parse out the data that we’re looking to use:
If($PSCmdlet.ParameterSetName -eq 'WebhookInput'){ $SearchResults = (ConvertFrom-Json $WebhookData.RequestBody).SearchResults.value Write-Output ("Target computer is " + $SearchResults.Computer) Write-Output ("Malicious IP is " + $SearchResults.RemoteIP) $ComputerName = (($SearchResults.Computer).split(' ') | Select-Object -First 1) $MaliciousIP = (($SearchResults.RemoteIP).split(' ') | Select-Object -First 1) } If ($ComputerName -like "*.*"){ $Sys = $ComputerName.Split('.') | Select-Object -First 1 } Else { $Sys = $ComputerName } You’ll notice that I’m doing some string formatting with our data here. Webhook data can concatenate multiple alerts together and separate the array by using spaces, so we’re splitting that up and grabbing the first entry for each input we need. The additional splitting on the ComputerName is to accomodate for systems that are domain joined, as Azure isn’t necessarily aware of a system’s FQDN. Mind you, this is a rough example, and continuously growing; So as my use cases evolve, so will my code.
Now that we have our data formatted, we can update our module and upload it to our Azure Automation Account using the same process outlined in Part I, but with the -Force parameter added so we can overwrite the existing instance.
Param( [Parameter(Mandatory=$true)] [object]$WebhookData ) $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 } } Set-AzureRmNSGMaliciousRule -WebHookData $WebhookDataNow, in a few minutes, our runbook should trigger and we can monitor the result.
$Job = (Get-AzureRmAutomationJob -RunbookName WebhookNSGRule -ResourceGroupName $AutoAcct.ResourceGroupName -AutomationAccountName $AutoAcct.AutomationAccountName) $Job[0] | Select-Object -Property * ResourceGroupName : mms-eus AutomationAccountName : testautoaccteastus2 JobId : 339601cd-14e9-4002-8fcd-7d2008726445 CreationTime : 7/24/2017 10:11:43 AM -04:00 Status : Completed StatusDetails : StartTime : 7/24/2017 10:12:21 AM -04:00 EndTime : 7/24/2017 10:13:31 AM -04:00 Exception : LastModifiedTime : 7/24/2017 10:13:31 AM -04:00 LastStatusModifiedTime : 1/1/0001 12:00:00 AM +00:00 JobParameters : {} RunbookName : WebhookNSGRule HybridWorker : StartedBy : We can start digging into the outputs of the runbook after completion to gather a little more data.
$Job = (Get-AzureRmAutomationJob -RunbookName WebhookNSGRule -ResourceGroupName $AutoAcct.ResourceGroupName -AutomationAccountName $AutoAcct.AutomationAccountName) $JobOut = Get-AzureRmAutomationJobOutput -Id $Job[0].JobId -ResourceGroupName $AutoAcct.ResourceGroupName -AutomationAccountName $AutoAcct.AutomationAccountName ForEach ($JobCheck in $JobOut){ $JobCheck.Summary } PS C:\WINDOWS\system32> ForEach ($JobCheck in $JobOut){ $JobCheck.Summary } Logging in to Azure... Target computer is server1 server1 server1 Malicious IP is 183.129.160.229 183.129.160.229 Target system is server1 Incoming MaliciousIP is 183.129.160.229 Creating rule... And now if I check against my system, we will see that OMS is auto-generating rules for us!
$VM = (Get-AzureRmResource).where({$PSItem.Name -like 'server1'}) $Machine = Get-AzureRmVM -ResourceGroupName $VM[0].ResourceGroupName -Name $VM[0].Name $NSG = (Get-AzureRmNetworkSecurityGroup -ResourceGroupName $Machine.ResourceGroupName).where({$PSItem.NetworkInterfaces.Id -eq $Machine.NetworkProfile.NetworkInterfaces.Id}) (Get-AzureRmNetworkSecurityRuleConfig -NetworkSecurityGroup $NSG[0]).where({$PSItem.Name -like "BlockedIP_*"}) Name : BlockedIP_206.190.36.45 Id : /subscriptions/f2007bbf-f802-4a47-9336-cf7c6b89b378/resourceGroups/test/providers/Microsoft.Network/networkSecurityGroups/server1nsgeus2domain Controller/securityRules/BlockedIP_206.190.36.45 Etag : W/"279e0fee-05c6-43ef-b897-19f927dd9a40" ProvisioningState : Succeeded Description : Auto-Generated rule - OMS detected malicious traffic from 206.190.36.45 Protocol : * SourcePortRange : * DestinationPortRange : * SourceAddressPrefix : 206.190.36.45 DestinationAddressPrefix : * Access : Deny Priority : 100 Direction : Inbound Name : BlockedIP_183.129.160.229 Id : /subscriptions/f2007bbf-f802-4a47-9336-cf7c6b89b378/resourceGroups/test/providers/Microsoft.Network/networkSecurityGroups/server1nsgeus2domain Controller/securityRules/BlockedIP_183.129.160.229 Etag : W/"279e0fee-05c6-43ef-b897-19f927dd9a40" ProvisioningState : Succeeded Description : Auto-Generated rule - OMS detected malicious traffic from 183.129.160.229 Protocol : * SourcePortRange : * DestinationPortRange : * SourceAddressPrefix : 183.129.160.229 DestinationAddressPrefix : * Access : Deny Priority : 101 Direction : Inbound After letting my system go for about 24 hours, my OMS Alert triggered the runbook an additional five times. Each time generating an additional network security group rule in response to traffic that OMS had recognized as potentially malicious, and thus remediating my problem while I slept.
Using a monitoring tool that can tightly integrate with your automation tools is a necessity in the age of the Cloud. I hope you enjoyed this series and find it to be useful!
Part I - Azure Automation Account Creation and Adding Modules
Part II - Configuring Azure Automation Runbooks And Understanding Webhook Data
Part III - Utilizing Webhook Data in Functions and Validate Results
Related Articles
PowerShell Escape Room
PowerShell Escape Room by Michiel Hamers by Michiel Hamers https://about.me/michielhamers/ Why on earth you want to create an Escape Room with PowerShell as backend? I’ve always been a fan of escape rooms, so I decided to create my own for my kids. I wanted to make it something that would be challenging and fun for them, but also educational. I decided to use PowerShell as the backend for the escape room, as I’m a PowerShell developer and I thought it would be a great way to learn more about the language.
Microsoft Graph PowerShell Module: Getting Started Guide
Microsoft Graph PowerShell Module: Getting Started Guide by Jeff Brown Microsoft is retiring the Azure AD Graph API sometime after June 30, 2023 (announcement). This retirement includes the Azure AD PowerShell module. In its place, Microsoft has released the Microsoft Graph PowerShell module. The Microsoft Graph PowerShell module is the next-generation way of managing Microsoft cloud services using PowerShell. If you have used MSOnline or Azure AD PowerShell in the past, you’ll need to read on to learn about this new module.
ICYMI: PowerShell Week of 08-October-2021
Topics include VMWare, Windows 11, Web Reports and more… Special thanks to Robin Dadswell, Prasoon Karunan V, Kiran Patnayakuni and Kevin Laux How to gather your vCenter inventory data with this VMware PowerShell script by Scott Matteson on 7th October Inventory reports are a common request when administering a VMware vCenter environment. Learn how this VMware PowerShell script can make such requests quick and easy Building a Web Report in PowerShell, use the -Force Luke by Chris Noring on 8th October