This is part six of my series on Azure Key Vault - now it's time to secure those secrets when using Azure Functions. There are a few ways of approaching the functionality, I'll write about the newest way, which should be the future way of doing it but I'll also show the "old" way of doing it - Part 8 of the blog series will be I'll be writing about Azure DevOps, which introduces yet another way of getting secrets into your Azure Functions app.

I'm a fanboy of Azure Functions, and really love the simplicity of writing a purpose-built function, ship and deploy it - the development cycle is super-fast. And the use cases for functions are many.

Managed Identity

The hardest part of setting up access between a Azure Functions app and Key Vault is setting up identity. Identity is a big and complex subject, but the Azure portal allows us to setup an identity simple and fast. 

First, we need two things

  • Azure Functions app
  • Azure Key Vault

The Azure Functions app

We start by enabling managed identity for the Azure Functions app. This time I'll do it via the Portal. Go to your Azure Functions app, choose "Platform features", and find "Identity".

Azure Portal - Azure functions - Identity

Within the Identity section, just enable it for system assigned identity (remember to click save):

Identity pane

That is it - What we just done is to create a Service Principal that is tied to the Azure Function app. The service principal can be used throughout Azure, when the identity of the Azure Function is needed. To verify you can find the identity in your Azure Active Directory under Enterprise applications, where its registered. For my case it is registered with the same name as the Azure Function app.

The Azure Key Vault

Next we need to go to the Key Vault and enable the newly created Service Principal to have access to it. This is done by going to the "Access Policies" section on the Key Vault. Here we need to add a new policy. Adding a policy will allow us to select the Service Principal we created for the Azure Function app and give it appropriate rights. I'd just give it "Secret Management", as it will give all rights related to Secrets, but it can be done more granular if needed. Once done, click save, and we have everything setup.

An Azure Function 

Once everything is setup, we can now access the secrets in the Key Vault. The way it is done, is by setting up the Azure Function app to read the secrets as Environment variables. The way it is done is a little "clunky", but it works. First let's create a secret in the Key Vault, name it "MySecret" and give it a value of "The secret is not that secret". Once the secret has been created, we can copy the Secret Identifier (the Uri) to the secret:

Copy Secret Identifier

In my case the Secret Identifier is: "https://keyvaultserieskeys.vault.azure.net/secrets/MySecret/4ba40c7350d4466da36ac7bf1f4108e9". The Identifier is a deep reference to the secret itself, but also to the specific version of it i.e. if it gets a new version, we need a new Identifier - or at least the last part of the Uri.

Now we have a secret, we need to setup the Azure Function app to get this secret added as an Environment variable. This is done like you'd do any other App Setting to the app. But the notation for adding it is special, as we need to tell the app to fetch the real secret from Key Vault, and not directly from the App Setting.

Get secret from KeyVault

The notation uses the following pattern "@Microsoft.KeyVault(SecretUri={SecretIdentifier})" - The notation will make the Azure Runtime grab the secret and add it to an environment variable that can be used when running the app. With the settings as above, we will now be able to fetch the value of the secret by typing

System.Environment.GetEnvironmentVariable("MySecret");

A simple implementation where it is used, to prove that it works is in this simple HttpTrigger Function - It will just grab the Environment variable and return it. It's used using the browser editor for Azure Functions, the but code is the same when doing a "real" c# function.

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task Run(HttpRequest req, ILogger log)
{
    var secretIs = System.Environment.GetEnvironmentVariable("MySecret");
    return (ActionResult)new OkObjectResult($"Secret is , '{secretIs}'");
}

The result of triggering the function is a string with the content "Secret is , 'The secret is not that secret'".

With the above in place, we now have a working integration between the Azure Key Vault and the Azure Functions app. If more secrets are needed, we just add them in the Azure Function app's settings.

Worth noticing

As of this writing the Key Vault reference notation - the @Microsoft.KeyVault syntax is still in preview - It's been so for about half a year, so it's been well tested by now, but might show some failures from time to time. Some of the things you need to be aware of are

  • Values are updated on app restart, this means that if you update a value in Key Vault, you need to restart the Functions app for it to kick in.
  • Values are referenced by their version, which means you can't just reference latest value. i.e. all references include a random version number in the end. This a limitation of the preview feature, and hopefully it will be possible to just reference the latest version once it leaves preview.
  • Local development - When developing your functions locally, you're not running with the same identity as the Functions app when its running on Azure. This also means that you can't access the secrets in the same way. This is ok though, as when developing locally you'd add the secrets to your "local.settings.json" file. Keep these local, and don't commit it to source control, and you are golden. Once the app is deployed, the settings will be overwritten by the ones specified on the Functions app.

Client Id and Client Secret

Even though the above method is the new and preferred way of accessing keys from Azure Key Vault in your Azure Functions applications, then the method I've mentioned earlier in the previous blog posts still apply. I'm referring to ClientId and Client Secret.

We can implement the exact same codes as we did previously to gain access to the Key Vault from an Azure App service. The reason this is doable is that an Azure Functions app is running on top of the Azure App service environment. This means that the Azure Functions app has access to the same things that you'd normally have. 

To use a Client Id and a Client Secret, you need to first have these - check part 2 where we created those. Then the code to access the secrets are the same as in part 3. We need to add NuGet references to 

With the two packages included, we can spin up the KeyVaultClient, which fetches the secrets from the Azure Key Vault. The entire function could look like this:

using System.Threading.Tasks;
using KeyVaultSeries.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.KeyVault;
using Microsoft.Extensions.Logging;

namespace KeyVaultSeriesFunctions
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, ILogger log)
        {
            KeyVaultClient keyVaultClient = new KeyVaultClient(TokenBasedAccessHelper.GetToken);

            var secretValut = (await keyVaultClient.GetSecretAsync(
                "https://keyvaultserieskeys.vault.azure.net/secrets/MySecret")).Value;

            return (ActionResult)new OkObjectResult($"Hello, {secretValut}");
        }
    }
}

The TokenBasedAccessHelper.GetToken can be found here: https://github.com/mikkelhm/Blog-KeyVaultSeries/blob/master/KeyVaultSeries.Core/TokenBasedAccessHelper.cs - only change is that ConfigurationManager.AppSettings needs to be swapped with Environment.GetEnvironmentVariable.

Worth noticing

The above method will allow you to implement Azure Key Vault in your Functions app in a different way, but with more control than in the first example. With this approach you can start by finding all the settings you need throughout your application, and only load these. Rather than having to setup every single secret as an App Setting, you could just have the base url to the Key Vault defined, and then fetch the necessary secrets.

Secrets can just be referenced by its name, and not by the specific version. This means you don't have to update anything in order to get the latest version of a secret.

With the newly introduced Dependency Injection for Azure Functions the secret management could be stuffed away in a service that is injected in the functions where it is needed. For larger Azure Function apps, I think this would be the approach.

The approach gives some more freedom, but also gives you more responsibility for managing the integration you self. I.e. we already implemented two NuGet packages and added custom code. The @Microsoft.KeyVault approach, does not need any custom code - it just works out of the box.

References