Implementing MSI in Azure Functions Step-by-step
Introduction
The management of secrets, credentials, certificates, and keys used to secure communication between services is a common obstacle for developers. Developers are no longer required to manage these credentials with managed identities.
Services need a way to access Azure Key Vault, even though developers can safely store secrets there. Applications can use managed identities to connect to resources that support Azure AD authentication by using an automatically managed identity in Azure Active Directory (Azure AD). Without having to manage any credentials, applications can obtain Azure AD tokens using managed identities.
In this tutorial blog, we will take a look at how we can implement the Managed System Identity (MSI) on an Azure Function from scratch using Azure Key Vault and Azure Storage Account. To elaborate further, we will create an Azure Function from scratch and try reading some data from Azure blob storage by connecting to it using the account key first, and then replacing the same with the MSI using Key vault. This will help you understand the difference between two and show how MSI accounts for the following benefits:
Creating the Azure Functions
In the Portal
Let us get started by creating an Azure Function by navigating to the Azure Portal as shown below:
This takes a couple of minutes. While this happens, pull up your Visual Studio (2022 preferred). Ensure you have .NET 6 installed to go ahead. If you don't have any of them, refer this and follow the steps before moving further.
Locally
Once ready with Visual Studio and .NET 6, open up your Visual Studio and click on new project as shown:
Search for Azure Function as shown and then choose the one indicated below and click Next:
Select the options as shown and click on Create to create the new Azure Function:
Executing the Function locally
Replace the code in the Run method of the Azure Function with the below code:
string name = req.Query["name"]
? ? ? ? ? ? string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
? ? ? ? ? ? dynamic data = JsonConvert.DeserializeObject(requestBody);
? ? ? ? ? ? name = name ?? data?.name;
? ? ? ? ? ? string accountName = Environment.GetEnvironmentVariable("SA_Name");
? ? ? ? ? ? string accountKey = Environment.GetEnvironmentVariable("SA_Key");
? ? ? ? ? ? string accountURL = "https://" + Environment.GetEnvironmentVariable("SA_Name") + ".blob.core.windows.net";? ? ? ? ? ??
? ? ? ? ? ?
? ? ? ? ? ? StorageSharedKeyCredential sk = new StorageSharedKeyCredential(accountName, accountKey);
? ? ? ? ? ? Uri uri = new Uri(accountURL);
? ? ? ? ? ? DataLakeServiceClient serviceclient = new DataLakeServiceClient(uri,sk);
? ? ? ? ? ? DataLakeFileSystemClient container =? serviceclient.GetFileSystemClient("test");
? ? ? ? ? ? DataLakeFileClient file = container.GetFileClient(name);
? ? ? ? ? ? return new OkObjectResult(file.OpenReadAsync().Result);;
Please note that I have renamed the name of the .cs file to TestMSI and hence I see the same while running the code. You may see Function1 or similar.
You may see some errors when you paste the above code. To fix them, follow the below steps:
Install the below nuget packages as shown:
To learn how to do the same, refer this blog.
Add the following using statements in the Function1.cs file if they are missing:
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Azure.Storage;
using Azure.Storage.Files.DataLake;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
Now, go to the Azure Portal and create a storage account or use an existing one. Upload a simple .txt file to any of the containers as shown:
Make sure the name of the container is test for the code to work seamlessly. If not, please update the below line in the code:
DataLakeFileSystemClient container =? serviceclient.GetFileSystemClient("YOUR_CONTAINER_NAME");
Get the Storage account name and Key and update the local.settings.json file as shown below in the code:
Try running the code and you shall see the below screen:
Navigate to the URL shown above in any browser. To run the code, enter the name of the file that you uploaded in Azure Storage Account container with the right path and click Execute. You shall receive the contents of the file as indicated in Response Body.
Deploying the Azure Function
Now, we need to deploy this code, which works fine locally, to the Azure Function that we created.
In?Solution Explorer in Visual Studio, right-click the project and select?Publish. In?Target, select?Azure?then?Next.
Follow the steps as shown:
Make sure you are logged in with the same Microsoft account in Visual Studio that you are using in the portal. If yes, then you should see the target Azure Function resource:
Skip the API Management for now as shown and proceed:
Select Publish and wait for it to succeed as shown:
领英推荐
Once done, you should see something similar:
To verify whether the deployment succeeded, go back to the portal and you must see your function name as shown:
Running the Azure Function
To test the Azure Function, copy the URL from the Overview pane of the Azure Function in the portal, paste in any browser of your choice and append "api/swagger/ui#/name/Run" to the same in the end. You should see the following screen. Try running the API endpoint as you did earlier in the localhost as shown:
You might face a 401 Unauthorized error because the Function key is missing and hence it does not allow you to make a call directly without passing it. To counter this, open the Azure function in the portal and go to Functions and choose your Function. Inside it, go to Function Keys as shown in the below image and copy the key.
Switch back to the browser and click on Authorize in the Swagger UI as shown:
Paste the keys as shown and click on Authorize.
Try rerunning the API and you shall see the 500 error this time indicating something is wrong with the code. Now, if you scroll a couple of steps back, we had used few values in the local.settings.json file while running the code in the local machine. The same values are missing from the Function App configuration after deployment. To fix this, go back to the Azure Function in the Portal and navigate as shown:
Add SA_Name and SA_Key as keys and their corresponding values as values from the local.settings.json to the application setting one-by-one as shown for a sample value SA_ConnectionString:
Click OK and do not forget to click Save after the settings are added.
Now, if you run the API, it should work as expected.
Implementing MSI
Now that we are able to build and deploy an Azure Function, we need to understand how the Azure Function is interacting with the Storage Account to get the contents of the specified file. As you would have guessed by now, it is the Account Key.
Now, the idea of MSI is to avoid using it directly in the Configuration of the Azure resource(Azure Function, in this case) without affecting the functioning of the applications. This is done by registering the Azure function resource in Azure Active Directory by turning on the Managed System Identity, creating a secret for the same in an Azure key Vault and then finally creating the reference for the same in the Configuration settings.
To implement the above, head over to the Azure Function in the portal and search for Identity in the left pane. Turn on the System Managed Identity as shown below:
Open any existing Azure Key Vault or create a new one and go to Secrets under the Objects section in the left pane. Click on Generate/Import and add the SA_Key configuration setting value as the value with the secret name as shown below and click on Create:
Once done, navigate to the Access policies section in the left pane of the Key vault and then click on Create. Choose the below fields and click Next:
Choose the Select a Service Principal option and search for the name of the Azure function and select it as shown below:
Keep on clicking next and Create the same. Verify as shown below:
Now, copy the Secret Identifier value from the newly created Secret as shown below:
Replace the value of the SA_Key Configuration setting in the Azure Function as shown below:
Refer the below snippet to understand it better:
????"name":?"SA_Key"
????"value":?"@Microsoft.KeyVault(SecretUri=PASTE_THE_COPIED_VALUE_HERE)",
Once this is updated, you should see the green tick indicating it is able to access the Key vault reference.
With this in place, you are all set to run the API!
As we see, the same application shall run without using the account key and specifying it anywhere in the Azure Function. The Key Vault secret and access policy takes care of the authentication internally.
Hope you enjoyed the article and learnt something new! Follow me and Stay tuned for the next one!
If you wish to read about something specific, do let me know in the comments and I shall be more than happy to deliver it and grow along!
Please like and share this article, if you find it useful. You can find my other articles on?Medium.
Subscribe to the newsletter to get my latest articles in your inbox.
Should you be having any queries feel free to reach out to me via?LinkedIn?or drop an email [email protected].
Code reference: https://github.com/Naman786sinha/.NET-Core-Development-with-Azure-and-Swagger/tree/master/MSI
Lead Software Engineer/Architect (Remote) - .NET, Azure, AWS
1 年Why do you need KeyVault if you use MSI? The whole point of MSI should be to stop using any keys and secrets altogether. Better go deep into using MSI instead.
Student at Indian Institute of Science (IISc)
1 年Useful stuff!
SDE3 @ Morgan Stanley
1 年Very well explained in simple steps.. Keep contributing ????