Tagging Azure Resources with a Creator

Published 06-21-2020 05:39 PM 6,393 Views
Microsoft

 

Update: Objects created by a Service Principal will tag the objects with a GUID instead of a name by default. You can fix this behavior by giving the Managed Identity the Application Developer role in Azure Active Directory. 

 

Hello everyone, my name is Anthony Watherston and I am a Senior Premier Field Engineer in Melbourne Australia. A question was recently raised by a colleague about how we can tag an Azure resource with the name of the person who created it. This would prove especially helpful in the case of resources that are older than the default activity log retention time where the owner cannot be identified from the initial creation event. Azure Policy would normally be used to update tags however policies can only act on the properties of the resource itself – the caller is only exposed through the activity log event which creates the resource.

 

Enter Event Grid Subscriptions and Azure Functions. Each time an activity occurs in an Azure subscription an event is generated, and we can subscribe to that event using an Event Grid Subscription – part of the information captured in this event will be the user who is making the call. The subscription will help me to identify that an event has occurred, and because it is only going to deploy a tag the code should be minimal so I can use an Azure Function.

 

So, my flow looks like this: -

Anthony_W_0-1592784724912.png

 

You can clone or download the project at https://github.com/anwather/TagWithCreator  and check out the code but let us go through the main parts.

 

I built my function using PowerShell because it is easiest for me – you can create a function by following the quick start guide at Quickstart: Create a function in Azure using Visual Studio Code but instead of selecting a HTTP trigger I chose an Event Grid trigger. The extension provided in Visual Studio Code will create all the necessary files and even provide you with a sample of what an Event Grid event looks like in a file called sample.dat.

 

The main script code is at /functions/TagWithCreator/run.ps1.The events are passed into the function as an object already so I can just get the fields I want out of the event and assign them to variables to use later.

 

 

 

 

$caller = $eventGridEvent.data.claims.name
Write-Host "Caller: $caller"
$resourceId = $eventGridEvent.data.resourceUri
Write-Host "ResourceId: $resourceId"

 

 

 

 

The script checks if a tag exists on the object before applying – if it needs to make change then I am using the Update-AzTag cmdlet to apply the tag.

 

 

 

 

if (!($tags.TagsProperty.ContainsKey('Creator')) -or ($null -eq $tags)) {
    $tag = @{
        Creator = $caller
    }
    Update-AzTag -ResourceId $resourceId -Operation Merge -Tag $tag
    Write-Host "Added creator tag with user: $caller"
}
else {
    Write-Host "Tag already exists"
}

 

 

 

 

As far as the code is concerned there is nothing else that is any different to a normal PowerShell script but be aware that it executes using PowerShell 6 so any Windows PowerShell commands that are specific to Windows will not work.

 

The Azure Function handles all the connections to Azure using a managed identity which will be created when we deploy it – I also assign permissions to that identity so it can make the changes to the resources.

 

By default, the scaffolded Azure Function will download the entire Az module on each cold start of the function which adds time to the execution, so to improve performance I specify each module and version required in requirements.psd1

 

 

 

 

# This file enables modules to be automatically managed by the Functions service.
# See https://aka.ms/functionsmanageddependency for additional information.
#
@{
    'Az.Accounts'  = '1.8.0'
    'Az.Resources' = '2.0.1'
}

 

 

 

 

Apart from writing the actual code in run.ps1 and changing the requirements.psd1 file there are no other changes to be made to the scaffolded code – easy!

 

Deploying the Azure Function requires the following resources in Azure – a Storage Account, App Service Plan, Function App, Application Insights, and the Event Grid Subscription. The first four objects are created using an Azure Resource Manager template and I have put instructions to create the Event Grid Subscription later.

 

The steps to deploy the ARM template and function code are as below: -

1. Ensure that you have the latest version of the Az PowerShell modules available.

2. Connect to Azure using Connect-AzAccount

3. Modify the /environment/deploy.ps1 script and change the values where indicated.

 

 

 

$resourceGroupName = "awfunctionsdev"            # <-- REPLACE the variable values with your own values.
$location = "australiasoutheast"                 # <-- Ensure that the location is a valid Azure location
$storageAccountName = "awfunctionsdev"           # <-- Ensure the storage account name is unique
$appServicePlanName = "AustraliaSoutheastPlan"   # <--
$appInsightsName = "awfunctionsdev"              # <--
$functionName = "awfunctionsdev"                 # <--

 

 

 

 

4. From a PowerShell prompt change into the /environment directory.

5. Run the deploy.ps1 script. The output should be like below.

 

2.gif

 

This script will create a resource group, deploy the ARM (Azure Resource Manager) template, change the permissions for the managed identity created for the app service and finally compress and publish the Azure Function. All that is left to do is create the Event Grid Subscription and start sending events.

 

To create the subscription, follow the steps below: -

  1. In the Azure Portal click in the search bar at the top and enter “event grid subscriptions” – select the item that appears with that name.
  2. Click the + button next to Event subscription
  3. Fill out the details as in the image below with a couple of points to note: -
    1. “Topic Type” should be subscription and select your subscription – you will have to do this for each subscription you want to monitor.
    2. “Filter To Event Types” – you should only select “Resource Write Success”
    3. Select “Azure Function” as the “Endpoint Type” and then follow the pop-out blade to select your function.
  4. Click on “Filters”
  5. In the “Advanced Filters” section add a key and value as below. This will stop the subscription from sending events for when a tag is written and retriggering the function app.

 

Anthony_W_2-1592784725026.png

 

Anthony_W_3-1592784725028.png

 

Note: There is also an ARM template to deploy the Event Grid subscription in the GitHub repository in the eventgrid-arm branch.

 

Time to test – I can create a simple virtual machine using the New-AzVM command. As resources are created, they will send events which are picked up by the Event Grid Subscription. Each event is then sent to the Azure Function endpoint which applies the tag if it does not already exist.

 

Anthony_W_4-1592784725031.png

 

So, there it is – a way to tag resources with the name of the creator, it is important to point out a few caveats around this solution.

  • It will not apply a tag if a creator tag already exists
  • If you deploy objects using a service principal, you may not see a name appear in the tag – instead, it will show the application Id for the service principal.
  • If an object is updated by any process this can cause the tag to be applied – it does not know if a resource is pre-existing before the function was implemented. If you wanted to deploy this in your environment, you could apply a policy to tag every object with a Creator tag and a default value before implementing the function. That way if someone makes changes to an already existing object the function will not alter the tag. You would then remediate all objects and disable the policy before creating the Event Grid subscription.
  • If you wanted to scope this to only tag virtual machines when they are created you could add another filter to the Event Grid subscription, like the image below.

 

Anthony_W_5-1592784725034.png

 

  • My testing cases are small and in no way should reflect your own testing.

 

Thanks for reading this post, if there are any issues or you would like to contribute to the code please submit a PR (pull request) or issue on the Github page.

 

Disclaimer:

The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.

 

21 Comments
Visitor

Nice one @Anthony_W

Is it a good opportunity to add "created date" as another tag at the same time?

Microsoft

@JamesCave absolutely - very easy to do although I already have Azure Policy which does this for me. You could do it via the function though at the same time - just another line of code. 

Update-AzTag -ResourceId $resourceId -Tag @{CreatedDate=$(Get-Date)} -Operation Merge
New Contributor

@Anthony_W 

 

This is fantastic!  Thank you.  I have deployed this into our tenant.  The only issue I am having is that all osdisks, either deployed from the marketplace or Devops are being tagged with the function name.  Have you seen this issue?  

Microsoft

@lmpalermo - I haven't seen that, are they being deployed in the same deployment as a virtual machine? My disks are tagged with 05250873-b7a8-4392-b112-cf3c65d72ee1 which is the application Id for the Compute Resource Provider.

New Contributor

@Anthony_W sorry for the delay in responding, I was out of the office.

 

Yes, the disks are being deployed with the VM.  I also see it with SQL Servers and SQL Databases.  All other resources are tagged correctly.

 

lmpalermo_0-1603889563391.png

 

Contributor

I suggest to remove auto download of dependencies and instead add them to Modules folder. In consumption plan this adds significant amount of time for function startup time and does not persistent between runs.

New Contributor

@Anthony_W 

 

Ran into an issue today where Frontdoor WAF Policies aren't being tagged.  The activity log shows a failure every 2 minutes.  Looks to be an issue with Frontdoor (see below).

 

Is there anyway to exclude this resource type from being tagged?

 

https://github.com/Azure/azure-powershell/issues/10104

 

"Apparently the Azure portal itself also makes use of the PATCH operation to add tags to a Front Door or WAF policy. Hence currently it's not possible to add tags to those resources via the Azure portal. I also tried adding tags to Front Door via an ARM template, which worked. But adding tags to a WAF policy via ARM didn't."

Microsoft

@lmpalermo - you would have to add a filter into the code to make it skip for the front door resources

Microsoft

@Gregory Suvalian Great idea!

New Contributor

@Anthony_W 

 

Yep, sorry, I meant to update my post.

 

I updated line 23 of run.ps1 to: 

$ignore = @("providers/Microsoft.Resources/deployments""providers/Microsoft.Resources/tags""providers/Microsoft.Network/frontdoor")
 
No more alerts on any frontdoor resources.
Occasional Visitor

Hi @Anthony_W ,

 

Thanks for this article! I've been testing out this solution and everything was working great until today. Now I'm getting a lot of resource "not found" errors when the function tries to apply tags.

 

The function is able to print the ResourceID for the VM or resource on which the tag is being applied like so:

 

2020-11-12T22:39:54.070 [Information] INFORMATION: ResourceId: /subscriptions/blablabla/resourcegroups/test/providers/Microsoft.Compute/virtualMachines/Test1

 

But when the Update-AzTag command is run with that same ResourceId, the resource can't be found. This error spits out on the console:


2020-11-12T22:39:54.867 [Error] ERROR: The Resource 'Microsoft.Compute/virtualMachines/Test1' under resource group 'test' was not found. For more details please go to https://aka.ms/ARMResourceNotFoundFix

 

I've spent most of my day troubleshooting this. According to the deployment logs the resources finish creating well before the function attempts to add the tags. I checked out the "more details" link provided with the error this morning but it wasn't very helpful. Interestingly, the link now redirects to Microsoft's home page.  (Edit: somehow I copy/pasted the link wrong, edited the link).

 

Am I the only one running into this? Or has something changed on the Azure side?

Microsoft

@biglyfellow  Try updating the runtime to version 3 as at https://github.com/Azure/azure-functions-powershell-worker/wiki/PowerShell-6-to-7-Migration-Guide as this fixed an issue for me today.

 

I haven't seen any issues with Update-AzTag, and I've been running it today as part of something else. Also can confirm the function is running ok in my environment.

 

Have you tried restarting the function app? Anything changed with the managed identity permissions?

Occasional Visitor

Thanks @Anthony_W I am using version 3. I just deployed a VM and tried manually running the Update-AzTag command from PowerShell, and it took somewhere around 20 minutes after the deployment before a tag could be applied. Something is probably wrong with the account. I'll keep on digging into it; there was an account notification earlier that said the status of deleted VMs was incorrect. Maybe that's related somehow. Thanks for your help!

 

biglyfellow_1-1605227384575.png

biglyfellow_0-1605227337640.png

 

Occasional Contributor

Would be great to support "StringNotContains" in the Advanced filters of Event Grid.

 

  • Use case 1 : filter on data.resourceUri where "StringNotContains" --> 
@("providers/Microsoft.Resources/deployments", "providers/Microsoft.Resources/tags", "providers/Microsoft.Network/frontdoor", "providers/microsoft.insights/autoscalesettings", "Microsoft.Compute/virtualMachines/extensions", "Microsoft.Compute/restorePointCollections", "Microsoft.Classic")
  • Use case 2 : filter on data.resourceUri where "StringNotContains" the Managed Resource Group of Databricks where no ressources can be tagged.
 
Did a feature request on the feedback.azure.com.
Jamesdld

 

Occasional Visitor

In case anyone else is running into the issue where the Azure function fails to tag resources, and it takes 20+ minutes for the Update-AzTag command to work after deployment, I have an update from Azure support. The 20+ minute delay is how long it takes Azure to replicate the newly-deployed resource information to the region in which the Update-AzTag command is executed. Azure commands are routed to a single region, and if a resource is deployed in a resource group in another region, that resource's info has to replicate to the region in which the command is executed before the command will be successful. I'm told Azure is working on reducing the replication delay, but for now, I think that this solution is only guaranteed to work if the resources are deployed in a resource group in the same region as the Azure Function.

 

My problem is that I deployed the Azure Function in the US East region, but the VMs being deployed are in resource groups in different regions around the world. According to Azure support, the resource group's location that the VMs are deployed in must match the region in which the tagging command is executed to avoid replication delays. For instance, if I deployed a VM in the US East region but the resource group is assigned to US West 2, the VM's info would still need to replicate from the US West 2 region to the US East region.

 

As a workaround, I'm going to export the write events and use them to tag resources an hour after each event is generated. That should be enough time for resource info to replicate across all Azure regions, and then I wouldn't have to worry about what region the tagging command is executed in.

Visitor

This is all working really well, and many thanks for such a great script. Unfortunately, I'm still getting a GUID when using a service principal to deploy resources via a pipeline, despite giving the managed ID the Application Dev role. It's not a big issue, but it would be nice to resolve, if you have any ideas

 

 

New Contributor

@nicktf 

 

Did you try this?  It worked for me.  "Objects created by a Service Principal will tag the objects with a GUID instead of a name by default. You can fix this behavior by giving the Managed Identity the Application Developer role in Azure Active Directory."

New Contributor

@nicktf

 

Sorry, I see you tried that.  I had the same issue the first time.  I removed the role on the ID and added it back.  Since then, the name instead of GUID is tagged on my resources deployed from Devops.

Visitor

@lmpalermo Thanks! - I just gave that a try and no joy, I'm still seeing the Object ID - Hmm. 

New Contributor

@nicktf

 

I just checked the SP account that my pipeline uses to deploy, and I assigned Application administrator, not Application Developer.  Might want to give that a try. 

Visitor

@lmpalermo - I'm an idiot - total user error - I was making my pipeline SPs accounts App Devs - it needs to be the SP which runs the tagging operation....

Version history
Last update:
‎Jun 22 2020 06:14 PM
Updated by: