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
- Using External Inputs in Azure Durable functions
- Azure Functions Configuration and Secrets Management
- Using Key Vault and Managed Identities with Azure Functions
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) { var keyVaultEndpoint = Environment.GetEnvironmentVariable("AzureKeyVaultEndpoint"); if (!string.IsNullOrEmpty(keyVaultEndpoint)) { // using Key Vault, either local dev or deployed var azureServiceTokenProvider = new AzureServiceTokenProvider(); var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); var config = new ConfigurationBuilder() .AddAzureKeyVault(keyVaultEndpoint) .SetBasePath(Environment.CurrentDirectory) .AddJsonFile("local.settings.json", true) .AddEnvironmentVariables() .Build(); builder.Services.AddSingleton<IConfiguration>(config); } else { // local dev no Key Vault var config = new ConfigurationBuilder() .SetBasePath(Environment.CurrentDirectory) .AddJsonFile("local.settings.json", true) .AddUserSecrets(Assembly.GetExecutingAssembly(), true) .AddEnvironmentVariables() .Build(); builder.Services.AddSingleton<IConfiguration>(config); } 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); }); } } }
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", "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/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