{"id":287,"date":"2024-01-29T16:44:23","date_gmt":"2024-01-30T00:44:23","guid":{"rendered":"https:\/\/www.dumpsterfirecomputing.com\/?p=287"},"modified":"2024-01-29T16:46:56","modified_gmt":"2024-01-30T00:46:56","slug":"creatortagger-a-whodunnit","status":"publish","type":"post","link":"https:\/\/www.dumpsterfirecomputing.com\/?p=287","title":{"rendered":"CreatorTagger &#8211; A Whodunnit"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>&#8220;Be careful which rocks you look under&#8230;&#8221;<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I had a thought the other day.  Probably not a very good one, but it led to some tinkering.  I wondered if it would be possible to leverage Azure Policy to add a tag to a resource denoting who built it.  There are some limitations to both Activity Log and Log Analytics Workspace (for sending logs), and while the resource group deployment events are available, the &#8220;who did it&#8221; seems lacking.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As it turns out, no.  You can&#8217;t use Azure Policy yet to do this specific task.  But Anthony Watherston wrote a unique solution to this leveraging an Event Grid Subscription, sending Write events to an Azure Function, which will tag the resource.  See his full blog post titled &#8220;<a href=\"https:\/\/techcommunity.microsoft.com\/t5\/core-infrastructure-and-security\/tagging-azure-resources-with-a-creator\/ba-p\/1479819\">Tagging Azure Resources with a Creator<\/a>&#8221; &#8211; it&#8217;s quite good, and he has some code available for deploying the solution.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What I wanted to do though was manually build the bits.  I&#8217;ve supported other people&#8217;s Azure Functions, but haven&#8217;t deployed my own.  I&#8217;ve looked at Event Grid, but never Subscriptions.  So rather than take Anthony&#8217;s code and just deploy it, I poked at things to recreate it manually.  This ultimately helped me understand all the parts and pieces, which then helped me understand his code more easily.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Note: I&#8217;ve paired this blog post with a <a href=\"https:\/\/youtu.be\/FFOEKgyEFrg\">YouTube video<\/a> demonstrating the process.<\/em>  <em>It should be worth noting I&#8217;m still learning about video editing, so judge accordingly!<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"483\" height=\"760\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image.png\" alt=\"\" class=\"wp-image-289\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image.png 483w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-191x300.png 191w\" sizes=\"auto, (max-width: 483px) 100vw, 483px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Creating the Function App, we&#8217;re leveraging PowerShell Core as the runtime.  <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"549\" height=\"554\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-1.png\" alt=\"\" class=\"wp-image-290\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-1.png 549w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-1-297x300.png 297w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-1-150x150.png 150w\" sizes=\"auto, (max-width: 549px) 100vw, 549px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Function Apps want a storage account for storing function code and other configuration files.  As with nearly everything Azure-related, Microsoft has a good set of <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-functions\/storage-considerations?tabs=azure-cli\">documentation<\/a> around considerations for the storage account.  I&#8217;ve opted to create a new storage account for this.  Mostly because my lab doesn&#8217;t have any shared accounts, but also because I prefer the isolation.  <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-functions\/functions-best-practices?tabs=csharp#storage-account-configuration\">Microsoft even recommends<\/a> a single storage account for each function app in production for performance reasons (though I&#8217;m sure there are security benefits as well).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rest of the settings were kept as default and then deployed.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"562\" height=\"375\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-2.png\" alt=\"\" class=\"wp-image-291\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-2.png 562w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-2-300x200.png 300w\" sizes=\"auto, (max-width: 562px) 100vw, 562px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once deployed, the first thing to do is enable a System assigned managed identity on the Function App.  This managed identity will be the account under which tag actions take place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In order to actually do that work, it&#8217;s going to need permissions.  I&#8217;ve scoped the permissions at the subscription level by creating two new role assignments for Read and Tag Contributor<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1023\" height=\"563\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-6.png\" alt=\"\" class=\"wp-image-295\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-6.png 1023w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-6-300x165.png 300w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-6-768x423.png 768w\" sizes=\"auto, (max-width: 1023px) 100vw, 1023px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"835\" height=\"377\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-5.png\" alt=\"\" class=\"wp-image-294\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-5.png 835w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-5-300x135.png 300w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-5-768x347.png 768w\" sizes=\"auto, (max-width: 835px) 100vw, 835px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">With the permissions established, the next step is to actually create the function.  I stumbled a few times with this, but on the Overview page of the function you can add it here<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"548\" height=\"676\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-7.png\" alt=\"\" class=\"wp-image-296\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-7.png 548w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-7-243x300.png 243w\" sizes=\"auto, (max-width: 548px) 100vw, 548px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The function will be triggered by an Event Grid action, and as you can see I just used the default name for the actual function itself.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"693\" height=\"605\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-8.png\" alt=\"\" class=\"wp-image-297\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-8.png 693w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-8-300x262.png 300w\" sizes=\"auto, (max-width: 693px) 100vw, 693px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">One of the things that tripped me up a little bit was the requirement for the Az module for PowerShell.  It tripped me up because I didn&#8217;t quite understand where that import needed to take place until I poked around and found the requirements.psd1 file located in the App Files section.  <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"631\" height=\"376\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-9.png\" alt=\"\" class=\"wp-image-299\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-9.png 631w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-9-300x179.png 300w\" sizes=\"auto, (max-width: 631px) 100vw, 631px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The entire Az module is massive, and it&#8217;s good practice to import only what you need.  From line 5 of the code they point you to <a href=\"https:\/\/www.powershellgallery.com\/packages\/Az\">https:\/\/www.powershellgallery.com\/packages\/Az<\/a>, where we can locate the latest major version of the two modules needed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last thing to do with the Azure Function is to plop in the code to do the tagging work.  I&#8217;m not going to replicate the code here, as the kudos need to go to <a href=\"https:\/\/techcommunity.microsoft.com\/t5\/core-infrastructure-and-security\/tagging-azure-resources-with-a-creator\/ba-p\/1479819\">Anthony<\/a> for coming up with a workable script (you can find all this code on his page).  I simply copy\/paste the code into the function and made a single change.  I preferred the output in the commented-out line 3 to the uncommented line 4.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"692\" height=\"392\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-10.png\" alt=\"\" class=\"wp-image-301\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-10.png 692w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-10-300x170.png 300w\" sizes=\"auto, (max-width: 692px) 100vw, 692px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">So, swapping those two lines, I saved the function.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last thing to do is set up the Event Grid Subscription which will send notifications to the Function App.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"831\" height=\"769\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-11.png\" alt=\"\" class=\"wp-image-302\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-11.png 831w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-11-300x278.png 300w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-11-768x711.png 768w\" sizes=\"auto, (max-width: 831px) 100vw, 831px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In the screen above, the Topic describes where we&#8217;re going to get events from.  For my example I want subscription-wide events.  By default, there are 9 event types that can be collected.  Remove all but &#8220;Resource Write Success&#8221;, which is all we care about.  If someone successfully deploys a resource, that&#8217;s when it&#8217;ll need a tag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, the endpoint type is the Azure Function, and when you click the &#8220;Configure an endpoint&#8221;, it&#8217;s simply a matter of targeting the Azure Function we built.  And that&#8217;s&#8230;..all it took!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"345\" height=\"151\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-12.png\" alt=\"\" class=\"wp-image-303\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-12.png 345w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-12-300x131.png 300w\" sizes=\"auto, (max-width: 345px) 100vw, 345px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Oh bugger.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you run into the above &#8211; don&#8217;t panic.  Turns out, the Microsoft.EventGrid resource provider in my subscription was not enabled.  If this happens to you, browse to your subscription, click the Resource Providers blade, and filter for the Event Grid.  Here&#8217;s what mine looked like at the time of the above error:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"925\" height=\"251\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-13.png\" alt=\"\" class=\"wp-image-304\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-13.png 925w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-13-300x81.png 300w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-13-768x208.png 768w\" sizes=\"auto, (max-width: 925px) 100vw, 925px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Highlight the Microsoft.EventGrid, click Register above, and it takes about a minute or so to enable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lastly, the test.  I built a plain old Virtual Network<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"716\" height=\"357\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-14.png\" alt=\"\" class=\"wp-image-305\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-14.png 716w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-14-300x150.png 300w\" sizes=\"auto, (max-width: 716px) 100vw, 716px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The above view is immediately after creation &#8211; the Virtual Network has a costcenter tag, which it had inherited from the Resource Group.  After another minute or so (probably less, actually), the new tag appeared with my name as the creator:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"734\" height=\"344\" src=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-15.png\" alt=\"\" class=\"wp-image-306\" srcset=\"https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-15.png 734w, https:\/\/www.dumpsterfirecomputing.com\/wp-content\/uploads\/2024\/01\/image-15-300x141.png 300w\" sizes=\"auto, (max-width: 734px) 100vw, 734px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I like this for a number of reasons.  In Azure, tags are case-sensitive.  So if you allow people to edit\/update tags manually, you&#8217;ll find &#8220;John Doe&#8221;, &#8220;john doe&#8221;, &#8220;doe, john&#8221;, or even &#8220;JohnDoe&#8221; all as separate unique tag values.  This takes that inconsistency out of it (in fact, the tag name and value could be sent through a string ToLower() function just to force some consistency in formatting).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This was a fun exercise and got me thinking a bit about Function Apps a little more &#8211; and a pretty inexpensive solutions as well!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&#8220;Be careful which rocks you look under&#8230;&#8221; I had a thought the other day. Probably not a very good one, but it led to some tinkering. I wondered if it [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-287","post","type-post","status-publish","format-standard","hentry","category-general"],"_links":{"self":[{"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=\/wp\/v2\/posts\/287","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=287"}],"version-history":[{"count":6,"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=\/wp\/v2\/posts\/287\/revisions"}],"predecessor-version":[{"id":311,"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=\/wp\/v2\/posts\/287\/revisions\/311"}],"wp:attachment":[{"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=287"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=287"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dumpsterfirecomputing.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=287"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}