Blog

Retrieve Azure Key Vault Secrets using Azure Functions and Managed Service Identity

What is Azure Key Vault?

Azure Key Vault is a cloud key management service which allows you to create, import, store & maintain keys and secrets used by your cloud applications. The applications have no direct access to the keys, which helps improving the security & control over the stored keys & secrets.

Azure Key Vault uses encryptions that are protected by hardware security modules (HSMs) and offers a reduced latency by benefitting from a cloud scale and global redundancy. Azure Key Vault requires very little configuration, making it very easy and fast to provision and start using the key management system.

More details on Azure Key Vault can be found on Microsoft docs HERE

What is Managed Service Identity?

Azure Key Vault avoids the need to store keys and secrets in application code or source control. However, in order to retrieve keys and secrets from Azure Key Vault, you need to authorize a user or application with Azure Key Vault, which in its turn needs another credential. Managed Service Identity avoids the need of storing credentials for Azure Key Vault in application or environment settings by creating a Service Principal for each application or cloud service on which Managed Service Identity is enabled. This Service Principal enables you to call a local MSI endpoint to get an access token from Azure AD using the credentials of the Service Principal. This token is then used to authenticate to an Azure Service, for example Azure Key Vault.

More details on Managed Service Identity can be found HERE

How to use Managed Service Identity to retrieve secrets from Azure Key Vault using Azure Functions

Enable Managed Service Identity on an Azure Function

Start by creating a new or opening an existing Azure Functions App. In this scenario, the Function App is named “SecurityFunctions”, which was created in the “Security” resource group.

Once the Function App is opened, select “Platform features” followed by “Managed Service Identity”

To enable Managed service identity for the selected Azure Functions app, select the “On”-option for “Register with Azure Active Directory” and click save.

As stated earlier, a local Managed Service Identity URL is used to generate a token which can be used when authorizing to other Azure Services. When activating Managed Service Identity on your Function App, two environment settings are added to the configuration of your Function app service.

  • MSI_ENDPOINT : the local URI for which your app can request tokens
  • MSI_SECRET: the secret used to request a token from the MSI_ENDPOINT

To make sure the environment variables have been correctly set, go back to the “Platform features”-menu of your function app and select “Console” from the Development Tools.

Type SET followed by enter, and validate if both variables are available for your environment:

Managed Service Identity has now been enabled for your Function App.

Create an Azure Key Vault

Next, we’ll create a new Azure Key Vault service. Using the Azure Portal, open the desired resource group or create a new one. In this sample, we will keep using the “Security”-resource group. In the Resource Group, click “Add” to add a new service and search for “Key Vault”.

Select “Key Vault, followed by “Create” in order to open the configuration of your new “Key Vault” service. Provide a name for the Key Vault, select your subscription and add it to a new or existing resource group.

Select your pricing tier. Currently 2 tiers are available (Standard and Premium) where the premium pricing tier provides you with HSM backed keys on top of the Geo availability offered in both Standard and Premium tiers.

When creating a new Azure Key Vault, and since we enabled MSI on our Azure Fuctions-service, we can already select the function as an additional Service Principal.

Click on “Access Policices”, followed by “Add New”. Click on “Select principal” and search for the Principal assigned to your “Azure Functions App”- service, in our case “SecurityFunctions”.

Select the Principal and set the required permissions. For the purpose of the Azure Function, we only require the principal to be able to Get secrets for the key vault:

Complete by clicking “Create” in the “Create Key Vault”-slice. Your newly created Key Vault Service will be created including the 2 principals which were added during configuration.

Add a new Secret to the Key Vault

In the Key Vault properties screen, click on “Secrets”, followed by Add:

Select “Manual” from the upload options and fill in the details of your Secret. Remember the name, as you will need this when testing the Azure Function later.

Add the Key Vault URI to the Azure Function application settings

In order to develop the Azure Function to retrieve secrets from our newly created Key Vault, we need the URI of our Azure Key Vault in order to compose a GET-URI to request a specific secret from the Key Vault.

In your Azure Function, select “Application settings” in the Overview-window.

Add a new a new “KeyVaultUri” app setting and provide the URI of your “Key Vault”-service.

Develop the Azure Function

Add a new Azure Function to your Function App service. In this sample, a C# Http triggered function will be used.

In order to generate the MSI Authentication Token and use the Key Vault client from C#-code, we will need some additional nuget packages. In order to add the nuget packages, select your Azure Function and click on “View Files”.

Click “Add” to add a new file to your function and name it “project.json”. Fill in the dependencies as displayed above to enable the necessary nuget packages download.

We can now implement the code needed for the Azure Function to retrieve secrets from Azure Key Vault.

Make sure to add the necessary using directives:

Next, create a new instance of the “AzureServiceTokenProvider”, which is part of the “Microsoft.Azure.Services.AppAuthentication”-nuget package which is currently still in preview. This package enables a service to authenticate to other Azure Services.

The “ServiceTokenProvider” will be used to generate the MSI Access Token using the MSI_ENDPOINT & MSI_SECRET from the Environment Settings of the Azure Function.

Next, we will create a new Key Vault Client using the KeyVaultTokenCallback of the Azure Service Token Provider.

In order to retrieve the desired secret from our Key Vault, using the KeyVaultClient, we need to compose the correct secret URI. This uses the application setting which we added to our Functions App service earlier:

Finally, we can request the secret using the ”GetSecretAsync”-method of the  KeyVaultClient. A KeyVaultErrorException will be thrown in case the secret does not exist in the Key Vault.

Full code of the Azure Function:


#r "Newtonsoft.Json"

using System.Net;
using System.Configuration;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.KeyVault.Models;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json;
using System.Text;

public static async Task Run(HttpRequestMessage req, TraceWriter log)
{
log.Info(“C# HTTP trigger function processed a request.”);

SecretRequest secretRequest = await req.Content.ReadAsAsync();

if(string.IsNullOrEmpty(secretRequest.Secret))
return req.CreateResponse(HttpStatusCode.BadRequest, “Request does not contain a valid Secret.”);

log.Info($”GetKeyVaultSecret request received for secret {secretRequest.Secret}”);

var serviceTokenProvider = new AzureServiceTokenProvider();

var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(serviceTokenProvider.KeyVaultTokenCallback));

var secretUri = SecretUri(secretRequest.Secret);
log.Info($”Key Vault URI {secretUri} generated”);
SecretBundle secretValue;
try
{
secretValue = await keyVaultClient.GetSecretAsync(secretUri);
}
catch(KeyVaultErrorException kex)
{
return req.CreateResponse(HttpStatusCode.NotFound, $”{kex.Message}”);
}
log.Info(“Secret Value retrieved from KeyVault.”);

var secretResponse = new SecretResponse {Secret = secretRequest.Secret, Value = secretValue.Value};

return new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(JsonConvert.SerializeObject(secretResponse), Encoding.UTF8, “application/json”)};

}

public class SecretRequest
{
public string Secret {get;set;}
}

public class SecretResponse
{
public string Secret {get; set;}
public string Value {get; set;}
}

public static string SecretUri(string secret)
{
return $”{ConfigurationManager.AppSettings[“KeyVaultUri”]}/Secrets/{secret}”;
}

Test the function using the following request and replace the “Secret” with the secret name which was created earlier