Accessing Key Vault from Azure Functions using Managed Identities
Yesterday, I showed how we can deploy Azure Functions with the Azure CLI. Today, I want to build on that and show how we can use the Azure CLI to add a "Managed Service Identity" (apparently now known simply as "Managed Identity") to a Function App, and then use that identity to grant our Function App access to a secret stored in Azure Key Vault.
And again I'll show you how the entire thing can be automated with the Azure CLI.
Step 1 - Create the Function App
The first step is to create our Function App, for which we need a Resource Group, a Storage Account, and an App Service Plan. I covered this in more detail yesterday, but here's the basic Azure CLI commands to provision a new Function App running on the consumption plan.
# create a resource group
$resourceGroup = "AzureFunctionsMsiDemo"
$location = "westeurope"
az group create -n $resourceGroup -l $location
# create a storage account
$rand = Get-Random -Minimum 10000 -Maximum 99999
$storageAccountName = "funcsmsi$rand"
az storage account create `
-n $storageAccountName `
-l $location `
-g $resourceGroup `
--sku Standard_LRS
# create a function app
$functionAppName = "funcs-msi-$rand"
az functionapp create `
-n $functionAppName `
--storage-account $storageAccountName `
--consumption-plan-location $location `
--runtime dotnet `
-g $resourceGroup
Step 2 - Assign a managed identity
We can use the az functionapp identity assign
command to create a "system assigned" managed identity for this Function App.
az functionapp identity assign -n $functionAppName -g $resourceGroup
The response will include the principalId
and tenantId
, and we can get hold of them later if we need to with the following commands:
$principalId = az functionapp identity show -n $functionAppName -g $resourceGroup --query principalId -o tsv
$tenantId = az functionapp identity show -n $functionAppName -g $resourceGroup --query tenantId -o tsv
We can also find this identity in Azure Active Directory with the following commands (Note that the "principal Id is also sometimes called the "object id"):
az ad sp show --id $principalId
# or
az ad sp list --display-name $functionAppName
Assigning an identity to our Function App means we'll have two new environment variables MSI_ENDPOINT
and MSI_SECRET
which enable applications to easy get an authentication token for this identity. However, we won't need to directly use them in this example.
Step 3 - Create a Key Vault and Store a Secret
You may already have a Key Vault available to you, but if not, the Azure CLI enables you easily to create one. For the purposes of this demo, let's create a new Key Vault in the same resource group and add a secret to it.
# Create a key vault
$keyvaultname = "funcsmsi$rand"
az keyvault create -n $keyvaultname -g $resourceGroup
# Save a secret in the key vault
$secretName = "MySecret"
az keyvault secret set -n $secretName --vault-name $keyvaultname `
--value "Super secret value!"
# view the secret
az keyvault secret show -n $secretName --vault-name $keyvaultname
Each secret has an identifier in the form of a URL, and we'll need it in order to access the secret value. Here's how we can get the identifier for our secret:
$secretId = az keyvault secret show -n $secretName `
--vault-name $keyvaultname --query "id" -o tsv
Step 4 - Grant the Managed Identity Read Access to Key Vault
By default, the managed identity for our function app cannot access Key Vault. We can grant it access for reading secrets only with the following command:
az keyvault set-policy -n $keyvaultname -g $resourceGroup `
--object-id $principalId --secret-permissions get
# to see the access policies added:
az keyvault show -n $keyvaultname -g $resourceGroup `
--query "properties.accessPolicies[?objectId == ``$principalId``]"
Step 5 - Add Application Settings referencing Key Vault secrets
To access secrets in Key Vault from our Function App we can create application settings whose value has the form @Microsoft.KeyVault(SecretUri=https://my-key-vault.vault.azure.net/secrets/my-secret/29e8f1b62cb34f3aa40f0757aea0388d)
.
Although it is possible to set these secrets using the Azure CLI, it's a pain because it gets into a mess escaping the secret value (or at least does so from PowerShell). I found I needed to use the rather obscure ^^
escape sequence to ensure that the final )
character made it into my function app settings:
az functionapp config appsettings set -n $functionAppName -g $resourceGroup `
--settings "[email protected](SecretUri=$secretId^^)"
When an app setting is defined like this, the Azure Functions runtime will use the Managed Identity to access the Key Vault and read the secret.
Step 6 - Accessing the secrets in Azure Functions
Once we've set this all up, an Azure Function can simply access the secret by reading the environment variable with the app setting name. Here's a very simple Azure Function I made to test this that simply allows you to access any environment variable.
[FunctionName("GetAppSetting")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
string settingName = req.Query["name"];
if (string.IsNullOrEmpty(settingName))
return new BadRequestObjectResult("Please pass a name on the query string");
log.LogInformation($"Requesting setting {settingName}.");
var value = Environment.GetEnvironmentVariable(settingName);
return new OkObjectResult($"{settingName}={value}");
}
So if you call this function and pass a name
query string parameter of Secret1
, you'll get the value stored in the Key Vault secret. You can also call it passing a name
of MSI_ENDPOINT
or MSI_SECRET
to see the value of the environment variables that were added as a result of enabling a system managed identity.
Summary
Managed Identities simplify the work required to grant your Function Apps the right to access secrets in Key Vault, and the whole process can be automated with the Azure CLI. To read more on using secrets in Key Vault with Azure Functions, check out this article by Jeff Hollan. And you can access my entire demo PowerShell script here which includes the additional steps of deploying the sample Function App, and using Kudu to get the function key so you can even automate calling the Azure Function without needing to go to the portal first to find the function key.
Comments
I have a problem with this. I'm just getting the url as a string rather than the value actually in Key Vault... any ideas for me?
Michael Brownnot sure what your issue is, but double check in the app settings section of the portal that your app setting definitely has the correct value
Mark HeathMaybe I'm missing something, but I can't see anything in the code that would actually access the key vault. From what I see, the code only outputs the value stored in the App Settings, which is actually just the URL to the secret, as Michael pointed out. I suppose the code to access the key vault is missing from this article. Should probably be something like this, courtesy of Justin Yoo:
Tuukkavar provider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(provider.KeyVaultTokenCallback));
var secret = await kv.GetSecretAsync("https://my-keyvault.vault.azure.net", "[secret key]")
.ConfigureAwait(false);
It really does work - the feature is called "Key Vault References" https://docs.microsoft.com/...
Mark HeathI suspect there is something wrong either with the syntax of your App Setting, or the Service Principal has not been granted access to the Key Vault. I found last time I tested it that it did take a few minutes for the secret value to be populated correctly after I added it.
It would be nice for App Service to have some way to indicate that retrieving the secret has failed
How would this work locally? Say I want to put settings from my KeyVault when debugging locally - this doesn't appear to work.
AdamI wondering the same thing. When using a WebApp you can use your AD user account to retrieve secrets instead of using the Identity of the App, but I don't see how it is possible in Azure Function.
AlexandreLocally you'd typically just use a local settings file (which isn't checked into source control) rather than trying to get settings from KeyVault. If you do want to get settings from key vault locally, then I don't think there's a way to make this technique work at the moment
Mark HeathThis is works for function triggers as well? So lets say I have the an Azure service bus trigger, that requires the connection string. Can this be injected from key vault?
Andrei ScutariuOr key vault with managed identity can be used just inside the Run() function method?
Not sure about that exact scenario. I'm hoping to see it become easier for the service bus connection itself to use a managed identity with functions in the future (may already be possible - has been a while since I last checked)
Mark Heath