Getting secrets from Key Vault in a PowerShell based Azure Function

I’m currently working on a project where I need to deploy and Azure Function, that gets a secret from Key Vault using it’s Managed Identity, and uses that secret to authenticate to an API. In a drawing, this is what I needed to do:

Not a big thing, but it took me some time to figure out a way to do it. Since Functions is just a web app, it means it has app settings, just like any other web app in Azure. These app settings can be set during deployment, if you deploy using ARM templates, so let’s check that out.

First make sure you pass the secret to your ARM template, with a securestring parameter, so it’s not flying around in your code and deployment history:

"keyVaultName": {
    "type": "string",
    "defaultValue": "cp",
    "metadata": {
        "description": "Short name for customer, fx cp for Cloudpuzzles"
    }
},
"secret": {
    "type": "securestring",
    "metadata": {
        "description": "description"
    }
}

This secret is added to our Key Vault, by using a resource type of Microsoft.KeyVault/vaults/secrets:

{
    "name": "[concat(parameters('keyVaultName'), '/', variables('secretName'))]",
    "type": "Microsoft.KeyVault/vaults/secrets",
    "apiVersion": "2019-09-01",
    "properties": {
        "value": "[parameters('secret')]"
    }
},

And on my Key Vault, I’m adding the Managed Identity of my Function to RBAC roles and giving it permission to read secrets:

{
    "type": "Microsoft.Authorization/roleAssignments",
    "apiVersion": "2020-04-01-preview",
    "name": "[guid(concat(resourceGroup().name, parameters('keyVaultName')))]",
    "scope": "[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]",
    "dependsOn": [
        "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
    ],
    "properties": {
        "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('keyVaultSecretsUserRoleId'))]",
        "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('functionAppName')),'2016-08-01', 'Full').identity.principalId]",
        "principalType": "ServicePrincipal"
    }
}

Next, we need to add an app setting with a value of:

@Microsoft.KeyVault(VaultName=KEYVAULTNAME';SecretName=SECRETNAME)

Optionally you can add secret version too – but this prevents you from doing key rotation, which you should. If you don’t supply a secret version, the latest key will be used.

VaultName=KEYVAULTNAME;SecretName=SECRETNAME;SecretVersion=SECRETVERSION

As you might have guessed KEYVAULTNAME and SECRETNAME is provided by you, through parameters or variables in your template. The easiest way to get this into a string you can use for your app setting, is doing a concat. I prefer to have this as a variable, and then use the variable in the actual app seting in the template – but you’ll see. First, let’s do a concat:

"secret": "[concat('@Microsoft.KeyVault(VaultName=', parameters('keyVaultName'), ';SecretName=',variables('secretName'),')')]"

So let’s check out our app setting, and the reference to the key vault secret:

{
    "name": "secret",
    "value": "[variables('secret')]"
},

Easy right? Notice the name of the app setting: “secret”. Now from within our Function, we can use PowerShell to retrieve this secret, by referencing an environment variable with the same name as our app setting:

$ENV:secret

In my case I use the secret to authenticate to an API, and it could look like this (I also have an app setting with the username and API URL):

$password = ConvertTo-SecureString -String $ENV:secret -AsPlainText -Force
$credentials = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $ENV:username, $password
Connect-ToMyApi -Url $ENV:apiUrl -ApplicationPassword $credentials

You can see a full example of this on my GitHub: https://github.com/jefutte/AzureFunctionKeyVaultSecret

Leave a Reply