Please note: I have written a follow-up to this blog post, detailing a new, better approach in my opinion

One of the services in Azure that I enjoy most lately, is Azure Functions. Functions are great for writing the small single-purpose type of application that I write a lot nowadays. However, last week I was struggling with the configuration of bindings using an Azure Key Vault and I thought I’d share how to fix that.

When you create a new Azure Function for writing a message to a queue every minute, you might end up with something like the code below.

public class DemoFunction
{
  private readonly ILogger _logger;
  private readonly IConfiguration _configuration;

  public DemoFunction(ILogger logger, IConfiguration configuration)
  {
    _logger = logger;
    _configuration = configuration;
  }

  [FunctionName(nameof(DemoFunction))]
  public async Task Run(
    [TimerTrigger("0 */1 * * * *")] TimerInfo timer,
    [ServiceBus("%queueName%", Connection = "serviceBusConnectionString", EntityType = EntityType.Queue)] 
      IAsyncCollector sericeBusQueue)
  {
    var loopCount = int.Parse(_configuration["loopCount"]);

    for (var i=0; i<loopCount; i++)
    {
      await sericeBusQueue.AddAsync(i.ToString());
    }

    await sericeBusQueue.FlushAsync();
  }
}

As you can see, I am using Functions V2 and the new approach to dependency injection using constructors and non-static functions. And this works great! Not being in a static context anymore is highly satisfying for an OOP programmer and it also means that I can retrieve dependencies like my logger and configuration through the constructor.

One of the things I am doing with my Function, is pulling the name of the queue and the connection string to connect to that queue from my application settings. For a connection string this is the default and for the name of a queue or topic, I can do that by using a variable name enclosed in %-signs. After adding the correct three settings to my application settings, this function runs fine locally. My IConfiguration instance is automatically build and filled by the Functions runtime and my queueName and connectionString variables are in my local.settings.json.

The problem comes when trying to move this function to the cloud. Here I do not have a local.settings.json, nor do I want to have secrets in my application settings, the default location for the Functions runtime to pull its settings from. What I want to do, is using an Azure Key Vault for storing my secrets and loading any secrets from there.

It might have been that my Google-fu has been failing, but unfortunately I have not find any hook or method to allow the loading of extra configuration values for an Azure Function. Integrating with the runtime was important for me, since I also wanted to grab values for the configuration of my Function from the configuration, not just configuration that was used in my function.

Anyhow, what I ended up doing after a while of searching was the following:

public class Startup : FunctionsStartup    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var services = builder.Services;

            var hostingEnvironment = services
                .BuildServiceProvider()
                .GetService<IHostingEnvironment>();

            var configurationBuilder = new ConfigurationBuilder()
                .SetBasePath(hostingEnvironment.ContentRootPath)
                .AddEnvironmentVariables();

            if (!hostingEnvironment.IsDevelopment())
            {
                {
                    var currentConfiguration = configurationBuilder.Build();
                    var tokenProvider = new AzureServiceTokenProvider();
                    var kvClient = new KeyVaultClient((authority, resource, scope) => 
                      tokenProvider.KeyVaultTokenCallback(authority, resource, scope));

                    configurationBuilder
                        .AddAzureKeyVault($"https://{currentConfiguration["keyVaultName"]}.vault.azure.net/", 
                          kvClient, new DefaultKeyVaultSecretManager());
                }
            }

            services.AddSingleton(configurationBuilder.Build());

            // More dependencies ...
        }
    }

The solution above will, if running in the cloud, use the Managed Identity of the Function plan to pull the values from a Key Vault and append them to the configuration. It works like a charm, however it feels a bit hacky to do override the existing configuration this way. If you find a better way, please do let me know!