Using Key Vault and Managed Identities with Azure Functions

This article shows how Azure Key Vault could be used together with Azure Functions. The Azure Functions can use the system assigned identity to access the Key Vault. This needs to be configured in the Key Vault access policies using the service principal. By using the Microsoft.Azure.KeyVault and the Microsoft.Extensions.Configuration.AzureKeyVault nuget packages, defining direct references in the Azure Functions configuration is not required. The secrets can be read directly from the Key Vault. This also has the advantage of referencing only the secret and not the direct version of the secret. The latest version of the secret is used (depending on the cache)

Code: https://github.com/damienbod/AzureDurableFunctions

Posts in this series

History

2021-03-07 Update packages and using DefaultAzureCredential for Azure Key vault access

2020-09-18 Updated Configuration, updated Nuget packages

The configuration is setup in the Startup class which inherits from the FunctionsStartup class. We use a string property AzureKeyVaultEndpoint which is used to decide if the Key Vault configuration should be used or not. For local development, Key Vault is not used, user secrets are used. For the Azure deployment, the AzureKeyVaultEndpoint is set with the value of your Key Vault. The configuration is read into the application and added as options to the DI.

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyAzureFunctions;
using MyAzureFunctions.Activities;
using System;
using System.Reflection;

[assembly: FunctionsStartup(typeof(Startup))]

namespace MyAzureFunctions
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddScoped<MyActivities>();

            builder.Services.AddOptions<MyConfiguration>()
                .Configure<IConfiguration>((settings, configuration) =>
                {
                    configuration.GetSection("MyConfiguration").Bind(settings);
                });

            builder.Services.AddOptions<MyConfigurationSecrets>()
                .Configure<IConfiguration>((settings, configuration) =>
                {
                    configuration.GetSection("MyConfigurationSecrets").Bind(settings);
                });
        }

        public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
        {
            var builtConfig = builder.ConfigurationBuilder.Build();
            var keyVaultEndpoint = builtConfig["AzureKeyVaultEndpoint"];

            if (!string.IsNullOrEmpty(keyVaultEndpoint))
            {
                // might need this depending on local dev env
                //var credential = new DefaultAzureCredential(
                //    new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });

                // using Key Vault, either local dev or deployed
                builder.ConfigurationBuilder
                        .SetBasePath(Environment.CurrentDirectory)
                        .AddAzureKeyVault(new Uri(keyVaultEndpoint), new DefaultAzureCredential())
                        .AddJsonFile("local.settings.json", true)
                        .AddEnvironmentVariables()
                    .Build();
            }
            else
            {
                // local dev no Key Vault
                builder.ConfigurationBuilder
                   .SetBasePath(Environment.CurrentDirectory)
                   .AddJsonFile("local.settings.json", true)
                   .AddUserSecrets(Assembly.GetExecutingAssembly(), true)
                   .AddEnvironmentVariables()
                   .Build();
            }
        }
    }
}

The local.settings.json contains the configurations for the Azure Functions. (No secrets). The AzureKeyVaultEndpoint has no value. If this was set with the URL of a Key Vault, this would activate the Key Vault for local development.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobsSecretStorageType": "Files",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "ASPNETCORE_ENVIRONMENT": "Development",
    //"AZURE_TENANT_ID": "your tenant",
    // To use the key vault in local debug, the Firewall needs to allow this
    //"AzureKeyVaultEndpoint": "your azure ad tenant"
    "AzureKeyVaultEndpoint": ""
  },
  "MyConfiguration": {
    "Name": "Lilly",
    "AmountOfRetries": 7
  }
}

The MyConfigurationSecrets class is used to hold the secret configurations.

namespace MyAzureFunctions
{
    public class MyConfigurationSecrets
    {
        public string MySecretOne { get; set; }
        public string MySecretTwo { get; set; }
    }
}

The configuration can be used then like any ASP.NET Core application. The services are added in the constructor and can be used as required.

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MyAzureFunctions.Activities
{
    public class MyActivities
    {
        private readonly MyConfiguration _myConfiguration;
        private readonly MyConfigurationSecrets _myConfigurationSecrets;

        public MyActivities(IOptions<MyConfiguration> myConfiguration, 
            IOptions<MyConfigurationSecrets> myConfigurationSecrets)
        {
            _myConfiguration = myConfiguration.Value;
            _myConfigurationSecrets = myConfigurationSecrets.Value;
        }

When deploying, the Azure Functions needs access to the Key Vault. The Azure Functions requires a system assigned Identity. You can activate this, or check that it is created in the Azure portal.

In the Azure Key Vault add a new Access policy.

Search for the required system Identity, ie your Azure Functions, and add the required permissions as your app needs.

The secret configurations are no longer required in the App.Settings of the Azure Functions.

When the functions are called, the actual version is used depending on the cache.

Links:

https://damienbod.com/2018/12/23/using-azure-key-vault-with-asp-net-core-and-azure-app-services/

https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings

https://docs.microsoft.com/en-us/azure/azure-functions/durable/

https://github.com/Azure/azure-functions-durable-extension

https://damienbod.com/2019/03/14/running-local-azure-functions-in-visual-studio-with-https/

Microsoft Azure Storage Explorer

Microsoft Azure Storage Emulator

Install the Azure Functions Core Tools

NodeJS

Azure CLI

Azure SDK

Visual Studio zure development extensions

2 comments

  1. […] Using Key Vault and Managed Identities with Azure Functions (Damien Bowden) […]

  2. So you have a dedicated class for the secrets MyConfigurationSecrets
    Presumably the values are in the Key Vault, but in which format?
    Are they entered as individual secrets (one secret for each property on MyConfigurationSecrets)
    Or as a single secret in the form of a json string?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: