Azure Tags – Automatically tag the Creator of a resource

Tags are one of the most useful features in Azure which allows for the grouping of resources using tags for various purposes including billing, charge-back, identifying resources of an application, grouping resources by department, identifying production/test/staging environments, etc.

One useful feature would be to use tags to identify who created a resource. This could be useful when there are multiple administrators or users who have write access to an Azure subscription and you need to identify who created what. Firstly, a call-out to the following github site providing much useful information on the topic – https://github.com/itaysk/azure-tagowner

Although the site only mentions automatically tagging of Resource Groups, and the challenge tagging individual resources, tagging of Resource Groups in my opinion doesn’t capture the necessary information. Users can always add more resources such as VMs to an existing Resource Group. Hence, I decided to implement a similar approach to using Log Analytics alerts together with a Powershell Function as I’m not much of a developer and am more comfortable with Powershell than C# or node.js.

First step is to implement a Log Analytics alert which gets triggered when particular resources are created and sends a webhook containing the Resource Id and user who created the resource. The following in an example of the alert which is triggered whenever a storage account, virtual machine, IP address, NSG or virtual network gets created.

AzureActivity
| where OperationNameValue =~ "Microsoft.Compute/disks/write" or OperationNameValue =~ "Microsoft.Compute/virtualMachines/write" or OperationNameValue =~ "Microsoft.Storage/storageAccounts/write" or OperationNameValue matches regex "Microsoft.Network.*write"
| where Properties contains "Created"
| where ActivitySubstatusValue contains "Created"
| project ResourceId, Caller

The difficulty with writing the log query is that Azure logs all “Create” and “Update” actions as a write. The adding of a tag is also considered a write or update to a resource. Therefore, you could very easily end up with an infinite loop where the action of adding the tag triggers the alert. Therefore, you would need to specify individual resource types as well as looking for the difference in the logs for when a resource is created vs when it is updated. For the items that I’ve selected (I haven’t tested with others such as Web Apps, SQL databases, etc), the log entries that I’ve found that are unique with a creation of a resource are the following:

 | where Properties contains "Created" 
| where ActivitySubstatusValue contains "Created"

After defining the trigger for the log analytics alert, I created an Action Group to send a Webhook. You will notice that there is also an option to select the Azure Function directly instead of a Webhook. I tried that but it seems that it doesn’t pass the output of the alert to the Azure Function if I do that. Rather, it only triggers the Azure Function to run. Therefore, I ended up using the Webhook action. The URL of the Webhook will be obtained once the Azure Function is created, but for now, obtain a URL from http://webhook.site and create a test resource (virtual network for example) to capture the contents of the triggered Webhook.

With the Azure Function itself, I created a PowerShell Core Function App running on a Consumption Plan which includes 1 million executions and 400,000 GB-s free a month. In most cases, this shouldn’t cost much if at all unless there are lots of other Azure Functions running during the month.

Once the Azure Function App is created, create a new Function, select In-Portal for the development environment, then select Webhook + API.

This will create a HttpTrigger1 function. Select function and click on the “Get function URL” link to get the URL to be used in the Webhook action URL in the previous step.

The next step is to create a Managed Service Identity (MSI) for the Function to have access to the subscription to write the tags. Create the MSI by going to the Platform features of the Function App and selecting Identity.

Set the Status to “On” to create the MSI for the Function App.

You will then need to grant the MSI access to the subscription to write tags to the resources. Select the Azure subscription, go to the Access control (IAM) section and add a role assignment for the MSI. I’ve used the Contributor role as an example for testing but it is highly recommended to create a custom role for this.

The following PowerShell script does the work of tagging the resources based on the Resource ID and username sent in the Webhook. Note that it has been written depending on the specific format of the Webhook and needs to be modified if the Webhook format of the results were to change.

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host “PowerShell HTTP trigger function processed a request.”

# Get the results of the webhook alert
$content = $Request.Body.SearchResult.tables.rows
$results = $content.split(“`r`n”)
Foreach ($result in $results) {
# Result is either a Subscription ID or User
If ($result -match “subscription”) {
# Result is a Subscription ID
$resID = $result
}
Else {
# Result is a user so write tag
$UserName = $result
Write-Host “Adding tag ProvisionedBy: $UserName for Resource: $resID”
# Check if resource has existing tags (Not null value)
$r = Get-AzResource -ResourceId $resID
If ($r.Tags) {
# Add ProvisionedBy tag to existing tags
$r.Tags.Add(“ProvisionedBy”,$UserName)
Set-AzResource -Tag $r.Tags -ResourceId $resID -Force
}
Else {
# No existing tags. Add new ProvisionedBy tag to resource.
Set-AzResource -Tag @{ ProvisionedBy=$result } -ResourceId $resID -Force
}
#Clear variables before next iteration
$resID = $NULL
$r = $NULL
}
}

$body = $Request.Body.SearchResult.tables.rows
$status = [HttpStatusCode]::OK

# Associate values to output bindings by calling ‘Push-OutputBinding’.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = $status
Body = $body
})

One useful feature is the “test” section where you can test the code without having to wait for the Log Analytics alert to trigger the webhook. Obtain an example of a triggered webhook from http://webhook.site and copy the contents into the Request Body section.

You can then trigger the Function to run with the test data and modify your code accordingly.

Please feel free to leave comments on how the code can be improved or to add additional resource types to the Log Analytics query.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.