Retrieving all tagged resources with PowerShell, Azure Functions and Managed Identity
Tags: azure, security, serverless
Published: 2020-10-08 20:45:49

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:

{% youtube UsyGUMo-33E %}

## 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](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash)

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
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y2e2vwopsuqs5yqwed87.png)

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`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h3q88p7jqhkk4bdij5y4.png)

## 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. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9u1b470jo8uq9d5z2p7q.png)

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](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour)

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](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview)

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). 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mzs4vrb47gjifa24tfjr.png)

We change the **Status** to **On** and hit **Save**. This will present us with the following message, which is what we want:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q4eserhn9288nqvmhtnf.png)

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:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8ci4pucfxbf6vgfn3lrk.png)

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)**. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y84gmbiggjk6n6isbawc.png)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hn23gczs44eqy8yiz9sw.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ezfhpi7cy3afpnmql2ps.png)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mqdnwnkx04c9f3a041ms.png)

## 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

Or hit us up on Twitter: [Christos](https://twitter.com/chritosmatskas) [John](https://twitter.com/AzureAndChill)