Retrieving all tagged resources with PowerShell, Azure Functions and Managed Identity
Christos Matskas
Posted on October 8, 2020
I'm always amazed how much power we have in our hands and how much we can achieve with only a few lines of code. During our regular stream yesterday on the #425Show we came across the following requirement: we needed to retrieve all resource groups in an Azure Subscription that were not tagged with a specific tag.
We can actually watch the recording here:
PowerShell to the rescue
I'm a big PowerShell fan, especially after Core as I can use it anywhere and everywhere. Even in Azure Functions! Therefore, my go-to solution to our problem was to write a PowerShell script that would run on an Azure Function. Unfortunately, we didn't manage to complete the task during the show so I took it upon me to finish up and write a nice blog post about it so you can use the same approach too.
Create the Azure Function with PowerShell
Firstly, make sure to update your Azure Function Core Tools. Get the instructions on how to install and run locally in this excellent doc
With the tools installed, we can go ahead and create our Azure Function. Open your favorite terminal and type:
func init
Make sure to select the right runtime - in this case PowerShell
The above command created the Function app, the shell of our Function. But it doesn't contain any code yet. For that, we need to create a function. In the terminal type:
func new
Implementing the logic in PowerShell
Let's open our Function in VS Code to do some editing. First, we want to define which dependencies we use. By default, PowerShell will pull the full Azure Powershell which is a lot. We only need a subset of these modules via a custom configuration in the requirements.psd1
. Let's open that file and add the following dependencies:
@{
# For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'.
'Az.Accounts' = '1.*'
'Az.Resources' = '2.*'
'Az.ResourceGraph' = '0.7.7'
}
Let's close and save the file. Next and last step is to add the Function code to implement our logic
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
Connect-AzAccount -Identity
$resourceGroupsToTag = @()
$rgs = Get-AzResourceGroup
$expirestag = "expires"
foreach ($r in $rgs) {
$tags = Get-AzTag -ResourceID $r.ResourceID
try {
if (-Not $tags.Properties.TagsProperty.ContainsKey($expirestag)) {
$resourceGroupsToTag += $r.ResourceGroupName
}
}
Catch {
Write-Host "error " + $r.ResourceGroupName
}
}
$body = $resourceGroupsToTag
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = $body
})
If you want to know what each line does, make sure to use the VS Code Code Tour
feature to explain how we implemented this line by line.
If you haven't used CodeTour before then you're missing out. It's an amazing Code extension that allows you to record and walkthrough with comments and then step through the tour. I love it, I believe it's transformational and I plan on using it more frequently going forward.
Install it today - link
Deploy the Azure Function using the VS Code extension, or whichever way you feel more comfortable (Azure DevOps or GitHub actions etc)
Configure the Managed Identity
The nice thing about our code is that we can authenticate and run the queries against our subscription without having to write any code, provide any accounts or credentials. To do this, we leverage the power of Azure Managed Identities
We can configure Managed Identities via PoSH, CLI or ARM etc. If you want to do it with PowerShell, the command is:
Update-AzFunctionApp -Name <YourFunctionAppName> -ResourceGroupName <YourResourceGroup> -IdentityType SystemAssigned
Connect-AzureAD
$managedIdentityId = (Get-AzureADServicePrincipal -SearchString '<yourFunctionAppName>').ObjectId
New-AzRoleAssignment -ObjectId $managedIdentityId -RoleDefinitionName "Contributor" -Scope "/subscriptions/<YourSubscriptionId"
If you prefer using the Azure portal to make it more visual, we have to go to the Azure Function App in the portal and click on the Identity tab. Ensure that we're in the System Assigned area (default).
We change the Status to On and hit Save. This will present us with the following message, which is what we want:
This takes a couple of seconds as it configure the App Registration for us in Azure Active Directory. If all worked well, we should see the following:
Now we have to configure the Role Assignments to allow our Managed Identity to pull the information we need in our PowerShell Function. Click on the Azure Role Assignments button and on the next page press the Add role assignment (Preview).
Make sure to select the right values
- scope -> Subscription
- subscription -> Needs to be the same as the one where our Managed Identity was created
- role -> Reader should suffice as we're only querying our resources
And with these few steps, our Managed Identity is set up and ready to be put in good use :)
Putting it to the test
We are now ready to put our Azure Function to the test and retrieve some data. Navigate to your Function and open the Code + Test tab. Press the Run button and see the magic happen
Where is the code?
You can get the full code (including the Code Tour) on GitHub: https://github.com/cmatskas/azfuncWithMsi
Summary
This was as fun little project that shows how you can use Managed Identity to securely run PowerShell code in Azure Functions. There are many cases where PowerShell is a lot more powerful and versatile in pulling Azure-related information so having a secure way to run it is invaluable.
We recently talked about this exact implementation on our Twitch stream. We also discussed alternative options using the Azure Resource Graph, which we plan to blog about here soon.
- Watch us live and be part of the discussion: https://aka.ms/425Show
- Catch up our videos on demand on YouTube https://aka.ms/425Show/YT
- Talk to us on Discord and ask your questions: https://aka.ms/425show/discord/join
Posted on October 8, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 8, 2020