|
| 1 | +--- |
| 2 | +title: "How to migrate to Managed Identities and test locally with local debug!" |
| 3 | +date: "2025-01-21" |
| 4 | +redirect_from : /How-to-migrate-an-Azure-Function-app-to-use-a-Managed-Identity |
| 5 | +coverImage: \assets\images\2025\howtomigratetotestmanagedidentities.png |
| 6 | +categories: |
| 7 | + - "programming" |
| 8 | +tags: |
| 9 | + - "AzureFunctions" |
| 10 | + - "ManagedIdentity" |
| 11 | +excerpt: "Recently I came upon the need to harden how some Azure Functions were setup. Specifically, they were Azure Functions setup to fire and run some c# code when a message was dropped into a message queue. They worked great, but we would much rather use the security of a Managed Identity to connect, instead using of using a connection string. This guide will cover migrating a function over to using a managed identity AND also how to do this on local dev" |
| 12 | +fileName: '2025-01-21-how-to-migrate-an-azure-function-app-to-use-a-managed-identity.md' |
| 13 | +--- |
| 14 | +Recently I came upon the need to harden how some Azure Functions were setup. Specifically, they were Azure Functions setup to fire and run some c# code when a message was dropped into a message queue. They worked great, but we would much rather use the security of a Managed Identity to connect, instead using of using a connection string. |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +So, the process I took here was to first build a new Function App of type Queue Trigger, and configure it to use a Connection String to connect. |
| 20 | + |
| 21 | +Once I had my Queue Trigger created, I looked at the docs for Azure Functions to see the requirements for using Managed Identities. |
| 22 | + |
| 23 | +[Docs Link](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference?tabs=blob&pivots=programming-language-csharp#configure-an-identity-based-connection) |
| 24 | + |
| 25 | +According to these docs, I need to update to Azure Queues Extension version 5.0.0 or later, so I did that first. |
| 26 | + |
| 27 | +Next, the docs say that to configure Azure Functions to use an MSI instead of an environmental variable for connectionString, we simply add a new variable to our `local.settings.json` file. |
| 28 | + |
| 29 | + |
| 30 | +```local.settings.json |
| 31 | +{ |
| 32 | + "IsEncrypted": false, |
| 33 | + "Values": { |
| 34 | + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", |
| 35 | + "AzureWebJobsStorage__queueServiceUri": "https://testorageaccount22.queue.core.windows.net/testiam" |
| 36 | + } |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +I added the `AzureWebJobsStorage__queueServiceUri` value, this is just set to the storage account I am using. According to the docs, when you're using Azure Function Extensions v5 or higher, setting this config value instructs the Azure Function runtime to try and connect using a MSI. |
| 41 | + |
| 42 | +>To me, this is extremely non-intuitive and very confusing, but that's how it's done right now. |
| 43 | +
|
| 44 | +Next, I updated the signature of my function to be sure it is referencing the value from my settings file. |
| 45 | + |
| 46 | +```c-sharp |
| 47 | +//from |
| 48 | +//public void Run([QueueTrigger("testiam2", Connection = "connectionString")] QueueMessage message) |
| 49 | +
|
| 50 | +//to |
| 51 | +public async Task MsiQueueAccess([QueueTrigger("testiam", Connection = "AzureWebJobsStorage")] QueueMessage message) |
| 52 | +
|
| 53 | +``` |
| 54 | + |
| 55 | +Note how the Connection contains 'AzureWebJobsStorage', while the same settings prefix is found in the `json` file as well. That specific value is what triggers the new MSI features. |
| 56 | + |
| 57 | + |
| 58 | + So let's see what happens when I fire it up! |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | +``` |
| 63 | +Host lock lease acquired by instance ID '000000000000000000000000527A19E4'. |
| 64 | +[2025-01-22T16:48:16.609Z] An unhandled exception has occurred. Host is shutting down. |
| 65 | +[2025-01-22T16:48:16.612Z] Azure.Identity: ManagedIdentityCredential authentication failed: Service request failed. |
| 66 | +[2025-01-22T16:48:16.614Z] Status: 503 (Service Unavailable) |
| 67 | +[2025-01-22T16:48:16.615Z] |
| 68 | +[2025-01-22T16:48:16.616Z] Content: |
| 69 | +[2025-01-22T16:48:16.616Z] {"error":"service_unavailable","error_description":"Service not available, possibly because the machine is not connected to Azure or the config file is missing. Error: missing required agent config properties. Current agent config: {Subscriptionid: Resourcegroup: Resourcename: Tenantid: Location: VMID: VMUUID: CertificateThumbprint: Clientid: Cloud: PrivateLinkScope: Namespace: CorrelationID: ArmEndpoint: AtsResourceId: AuthenticationEndpoint:} (config file location: C:\\ProgramData\\AzureConnectedMachineAgent\\Config\\agentconfig.json). Connection status: Disconnected. Check Agent log for more details.","error_codes":[503],"timestamp":"2025-01-22 11:48:16.5491276 -0500 EST m=+544299.660283901","trace_id":"","correlation_id":"97f841ea-68cd-4521-8278-3e8537341acf"} |
| 70 | +``` |
| 71 | + |
| 72 | +### Do I even have a Managed Identity Yet? |
| 73 | + |
| 74 | +Hmm, that does not look like success. I noticed that the Subscription, Rg and all of those fields are all empty, likely because my dev laptop does not have an MSI on it. I was assuming this would be smart enough to try and use the same flow as DefaultAzureCredential, but I guess not. |
| 75 | + |
| 76 | +So let's instead enroll my machine in ARC to see what happens when there is an MSI associated... |
| 77 | + |
| 78 | +With that done I can now view the resource in Azure and see the Managed Identity which was created for me. |
| 79 | + |
| 80 | +You can view the MSI created for an Arc device by looking at the 'Resource JSON view', and see it's GUID. |
| 81 | + |
| 82 | + |
| 83 | + |
| 84 | +Now, to go to my storage account and give this identity (which Helpfully I can assign just by the name of the machine, not the guid displayed) some permissions to interact with the storage account. |
| 85 | + |
| 86 | +I assigned it 'Storage Blob Data Owner' perms and then restarted the app. |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +Now I have a different problem, instead of an error saying that I don't have an Managed Identity, I get this error that I can't access the Tokens directory. |
| 91 | + |
| 92 | + |
| 93 | + |
| 94 | +To fix this, I navigated to the directory and gave myself owner rights (suitable for debugging, and not needed in prod when this is deployed to a *real worker* with a *real MSI*). |
| 95 | + |
| 96 | +And now a different error. The storage account does not exist? |
| 97 | + |
| 98 | + |
| 99 | + |
| 100 | +OH I had the format of the URL incorrect, it should not include the name of the queue as I had done. |
| 101 | + |
| 102 | +``` |
| 103 | +Before |
| 104 | + "AzureWebJobsStorage__queueServiceUri": "https://testorageaccount22.queue.core.windows.net/testiam" |
| 105 | +
|
| 106 | +After |
| 107 | + "AzureWebJobsStorage__queueServiceUri": "https://testorageaccount22.queue.core.windows.net/" |
| 108 | +``` |
| 109 | +And now to restart once more...and now a new error! |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | +I thought about it and realized I granted Azure Blob Storage Data owner perms but don't recall doing anything related to queues, so lets try adding that too. I specifically added the `Azure Queue Storage Contributor` role this time and when I fire it up.... |
| 115 | + |
| 116 | +Success! |
| 117 | + |
| 118 | + |
| 119 | +## Quick Takeaways |
| 120 | +The big takeaways here are: |
| 121 | + |
| 122 | + - It IS possible to test Managed Identities on a normal dev machine or laptop, you'll just need to enroll the machine in Arc to assign a managed identity |
| 123 | + - You can use the automatic System Assigned MSI for your machine or any user created MSI |
| 124 | + - You will need to assign these permissions to the Managed Identity |
| 125 | + - - Subscription Reader for the sub holding the Storage Account |
| 126 | + - - Storage Data Owner permission for the Storage Account |
| 127 | + - - Storage Queue / Table / Blob Contributor for the Storage Account |
| 128 | + - When assigning to Prod, instead use User Assigned Managed Identities so one MSI can be shared on your prod devices within a region, for ease of management |
| 129 | + |
| 130 | +Some other weird takeaways is that all of this is configured using appSettings.json files, and namely some properties with very unintuitive names, like `AzureWebJobsStorage_StorageAccountUri`. To me, this feels like something that will likely change in a future release, so I would keep my eyes on this area. |
| 131 | + |
| 132 | +## TLDR |
| 133 | + |
| 134 | +The three most important things are: |
| 135 | +* be sure you upgrade to Extensions version 5 or higher |
| 136 | +* change your Queue Triggers Connection parameter to match the *prefix* of the setting in your json file (see example image below) |
| 137 | +* go to your `local.setttings.json` file and add a setting of `prefix___queueServiceUri` for queues, or the other values for other types of Azure Storage based triggers |
| 138 | + |
| 139 | +Service Type | Value |
| 140 | +--|-- |
| 141 | +Azure Blob Service | AzureWebJobsStorage__blobServiceUri |
| 142 | +Azure Queues | AzureWebJobsStorage__queueServiceUri |
| 143 | +Azure SQL Tables | AzureWebJobsStorage__tableServiceUri |
| 144 | + |
| 145 | +## Wait, prefix what? |
| 146 | + |
| 147 | +I know, I know, this stuff was rather hard for me to understand too and took a half a day to figure it out. |
| 148 | + |
| 149 | + |
| 150 | + |
0 commit comments