I'm creating an application that will run using the Azure Functions runtime hosted in an Azure Web app. The application will call a remote API, which is secured by a API token and will use Azure Blob storage, which is secured using an account key that is part of a connection string.
This application will use a Key Vault to store the API token and the blob storage connection string. Listed below is a summary of an ARM template that can create the secure infrastructure for the code.
{
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[variables('appInsightsName')]"
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2016-09-01",
"name": "[variables('hostingPlanName')]"
},
{
"apiVersion": "2015-08-01",
"type": "Microsoft.Web/sites",
"name": "[variables('functionAppName')]",
"kind": "functionapp",
"identity": {
"type": "SystemAssigned"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "appsettings",
"type": "config",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', variables('functionAppName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
],
"properties": {
"AzureWebJobsStorage": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('kvSecretStorageConnectionStringResourceId')).secretUriWithVersion, ')')]",
}
}
]
},
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2016-10-01",
"name": "[variables('keyvaultName')]",
"accessPolicies": [
{
"tenantId": "[reference(concat('Microsoft.Web/sites/', variables('functionAppName')), '2019-08-01', 'Full').identity.tenantId]",
"objectId": "[reference(concat('Microsoft.Web/sites/', variables('functionAppName')), '2019-08-01', 'Full').identity.principalId]",
"permissions": {
"secrets": [ "get" ]
}
}
],
},
"resources":[
"type":"secrets",
"apiVersion":"2016-10-01",
"name":"[variables('kvSecretStorageConnectionStringName')]",
"dependsOn":[
"[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
],
"properties":{
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2018-02-01').keys[0].value)]"
}
]
}
Template notes:
- Creates a storage account
- Creates a hosting plan
- Creates a key vault
- Creates a key vault secret containing the connection string for the storage account
- Creates a Function App with a system-managed identity
- Adds an access policy to the Key Vault for the Function App's identity, allowing it to read secrets
- Sets appSettings of the function app containing a Key Vault reference for the AzureWebJobsStorage property
If you view the configuration of the Function App in the portal, you will see that the setting is sourced from Key Vault.
![](https://www.schaeflein.net/content/images/2020/04/StartToFinishAppSetting1.png)
![](https://www.schaeflein.net/content/images/2020/04/StartToFinishAppSetting-1.png)
The setup is for the API token would follow the same pattern as the connection string, but would require a parameter since the value is not generated within the template.
The code for the function app is straight-forward. Simply use environment variables to get the connection string:
string storageConnectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
And, use a local.settings.json
file to set the values on the dev machine:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}