5 min read

3 Ways Referencing Azure Key Vault from Azure Functions

Justin Yoo

Almost of all time, Azure Functions or Azure App Service uses sensitive information like API auth key or database connection strings. Azure Key Vault helps to store that confidential information safely. Throughout this post, I'm going to show three different ways to get references to Azure Key Vault from Azure Functions and discuss their pros and cons.

You can download the sample code from KeyVault Reference Sample.

How Key Vault Reference Works on Azure Functions Instance

First of all, let's have a look at how an Azure Functions instance gets a reference to Azure Key Vault.

As you can see the image above, the values set by reference shows Key Vault Reference at the Source column.

By the way, the Azure Functions app instance recognises those Configuration values as environment variables. Therefore, within the code, we use the Environment.GetEnvironmentVariable() method to get the configuration values.

We can get those values by deserialisation, but this is beyond the topic of this post.

How can the secret values from Key Vault be converted into environment variables? Our application doesn't know whether it is the reference value or environment variables. It is because the App Service instance internally refers to the Key Vault values and converts them into environment variables. As the logic around this is all encapsulated, we don't know how it works. We just set the reference and use the value. Full stop.

Key Vault Reference #1

The official document recommends the following two ways for reference.

@Microsoft.KeyVault(SecretUri=https://<key-vault-name>.vault.azure.net/secrets/<secret-name>/<secret-version>)
@Microsoft.KeyVault(VaultName=<key-vault-name>; SecretName=<secret-name>; SecretVersion=<secret-version>)

Although we use the reference like above, our code doesn't change. We still call Environment.GetEnvironmentVariable("Hello") or Environment.GetEnvironmentVariable("Hello2").

This approach has a caveat. When we update a secret, its version is also changed. Whenever the version changes, we have to update the reference like SecretUri or SecretVersion as the version is a part of the reference. We can use Azure Event Grid because Azure Key Vault raises an event when the secret changes, which can be captured and handled. But this requires extra coding for event handling.

Key Vault Reference #2

The second approach for referencing without needing extra coding is to use this recipe. When we put the secret identifier URL to SecretUri, simply omit the secret version like below. Make sure that the URI MUST end with the trailing slash (/).

@Microsoft.KeyVault(SecretUri=https://<key-vault-name>.vault.azure.net/secrets/<secret-name>/)

With this approach, the reference always takes the latest version of the secret from Key Vault.

There is a caveat on this approach. As we discussed above, the reference is converted to environment variables. As the environment variables are cached, they won't change until the app instance is refreshed. Updating app settings blade results in the app instance being refreshed. But, the SecretUri value without the secret version won't have a chance to get the app instance refreshed. Therefore, the old value still remains. Of course, it is refreshed, but we can't control it unless we refresh the instance.

Key Vault Reference #3

The Key Vault reference syntax, @Microsoft.KeyVault(...) only applies when the app is deployed to Azure. In other words, it does not apply to the local dev environment. Therefore, if you want to use this reference for your local debugging, we need some extra coding. Let's have a look at the code below. First of all, declare the regular expression instances for the reference syntax pattern.

public class AppSettingsHandler
{
private static Regex regexSecretUri = new Regex(@"\@Microsoft\.KeyVault\(SecretUri\=(.*)\)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static Regex regexVaultName = new Regex(@"\@Microsoft\.KeyVault\(VaultName\=(.*);\s*SecretName\=(.*);\s*SecretVersion\=(.*)\)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
...
}

Write the GetValueAsync(string key) method to check environment variables. If the environment variable doesn't follow the Key Vault reference format, return the value.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
var reference = Environment.GetEnvironmentVariable(key);
if (!this.IsKeyVaultReference(reference))
{
return reference;
}
...
}
private bool IsKeyVaultReference(string value)
{
return value.StartsWith("@Microsoft.KeyVault(");
}
}

If the variable follows the reference format, then use the regular expression to check SecretUri and parse the reference URL.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
...
var bundle = default(SecretBundle);
var match = regexSecretUri.Match(reference);
if (match.Success)
{
var uri = match.Groups[1].Value;
bundle = await this._kv.GetSecretAsync(uri).ConfigureAwait(false);
return bundle.Value;
}
...
}
}

If the variable follows the VaultName format, then use the other regular expression to parse VaultName, SecretName and SecretVersion to get the secret value.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
...
match = regexVaultName.Match(reference);
if (match.Success)
{
var vaultName = match.Groups[1].Value;
var secretName = match.Groups[2].Value;
var secretVersion = match.Groups[3].Value;
bundle = await this._kv.GetSecretAsync($"https://{vaultName}.vault.azure.net", secretName, secretVersion).ConfigureAwait(false);
return bundle.Value;
}
...
}
}

If none of the above works, return null.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
...
return null;
}
}

Then replace the existing Environment.GetEnvironmentVariable() methods with this AppSettingsHandler.GetValueAsync() methods so that we can use the reference expression in our local development.

This approach also has a caveat. It works perfectly WITHIN the function method, but not within the triggers and bindings because both triggers and bindings directly access to the environment variables, not through the handler above.


So far, we've looked three different ways to get Azure Key Vault references from Azure Functions. As all three approaches have their own pros and cons, I can't say which one you should use. I'll leave that to you to make the right decision for your organisation.