Since security is becoming an increasingly important topic nowadays, I figured.. Why not write a blog about it? In this blog I am going to explain how you can remove all important (generated) secrets, such as connection strings, access keys, etc. from your appsettings files. Instead, we’re going to import them straight into the KeyVault as soon as they roll out of the infrastructure as code (IaC) templates.
The best password, is a password you haven't seen before.
Assumptions
Before we continue, I want to define some assumptions. These are the things I expect you already use in your current environment.
- An Azure environment + credits
- You have an App Service or Function already hooked up to Azure App Config + Azure Key Vault
- You are currently deploying your IaC with Bicep
Okay, let’s dive into the Bicep templates. I’d assume your master template looks something like this. There should be a template for an App Service, an App Configuration and a Key Vault. This is an example of how it could look like:
module AppConfiguration 'templates/AppConfiguration/configurationStores.bicep' = {
name: appConfigurationName
params: {
appConfigurationName: appConfigurationName
location: location
skuName: 'free'
}
}
module KeyVault 'templates/KeyVault/vaults.bicep' = {
name: keyVaultName
params: {
keyVaultName: keyVaultName
location: location
}
}
module AppServiceApi 'templates/web/sites.bicep' = {
name: appServiceApi
params: {
webAppName: appServiceApi
appServicePlanName: 'appServicePlanName'
linuxFxVersion: 'DOTNETCORE|6.0'
useManagedIdentity: true
location: location
alwaysOn: true
}
}
Adding a storage account
For some reason, you decide you want to add a storage account. Its access key must be added to the Key Vault, without you (the user) ever knowing what the value is.
First, we create a storage account using Bicep. We can then access its access key by using the script on line 10. This is the value that we should add to our Azure Key Vault.
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
var storageAccountKey = storageAccount.listKeys().keys[0].value
Adding the value to Key Vault
This step is actually pretty simple, if you know what you’re doing.. I’ve created a simple template that allows you to add a single key-value pair to your Key Vault. You only have to provide the keyVaultName keyVaultKey, and keyVaultValue.
Please take a look at highlighted lines 14-16 in the code below. Whenever we’re adding a KeyVault reference to Azure App Configuration, Azure requires it to be in an object with a uri property. If you forget this object, your reference to Key Vault will sadly result in an error.
As you can see in the screenshot below, the first reference is invalid. The other two are correct.
You can copy-and-paste the template below.
secrets.bicep
param keyVaultName string
@secure()
param keyVaultValue string
param keyVaultKey string
resource KeyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' {
name: '${keyVaultName}/${keyVaultKey}'
properties: {
value: keyVaultValue
}
}
var keyVaultRef = {
uri: KeyVaultSecret.properties.secretUri
}
output keyVaultUri object = keyVaultRef
How do I prevent this security risk?
It’s actually quite simple. I get that you don’t want to put all your Azure resource creation in one single file. That would become a total mess. What you can do is still create all your resources with the use templates. Then in another template, where you insert your Key Vault secrets and App Configuration settings, you can reference existing resources and retrieve the keys without passing them through the output variables.
Take a look at storageReference.bicep. Here you can see the ‘existing‘ keyword on line 1 (You might have to scroll a bit to the right). When using this keyword, you only have to provide the resource name. You can then treat it as just another resource and, for example, retrieve the resources’ key.
storageReference.bicep
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' existing = {
name: storageAccountName
}
var storageKey = storageAccount.listKeys().keys[0].value
Adding the Key Vault reference to Azure App Configuration
Now, for the final part. We’re going to add our reference to App Config. With the following template, we can decide whether to add a normal string value, or a key vault reference to our App Configuration. You can copy-and-paste the template below.
singleKeyValue.bicep
param appConfigName string
param appConfigKey string
param appConfigValue string
@allowed([
'string'
'application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8'
])
param contentType string
resource AppConfigurationResource 'Microsoft.AppConfiguration/configurationStores@2021-03-01-preview' existing = {
name: appConfigName
}
resource ConfigStoreKeyValue 'Microsoft.AppConfiguration/configurationStores/keyValues@2021-10-01-preview' = {
parent: AppConfigurationResource
name: appConfigKey
properties: {
value: string(appConfigValue)
contentType: contentType
}
}
How will it look when everything is put together?
In the following code block, I will show you all the code together. With this example, you can start expanding this yourself. And you’ll be rid of your secrets in no-time!
module AppConfiguration 'templates/AppConfiguration/configurationStores.bicep' = {
name: 'AppConfiguration'
params: {
appConfigurationName: appConfigurationName
location: location
skuName: 'free'
}
}
module KeyVault 'templates/KeyVault/vaults.bicep' = {
name: 'KeyVault'
params: {
keyVaultName: keyVaultName
location: location
}
}
module AppService 'templates/WebApp/WebApp.bicep' = {
name: 'AppService'
params: {
webAppName: webAppName
appServicePlanName: appServicePlanName
location: location
linuxFxVersion: 'DOTNETCORE|6.0'
useManagedIdentity: true
appSettings: [
{
name: 'AppConfig__Endpoint'
value: AppConfiguration.outputs.endpoint
}
]
}
}
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
module StorageKeyKeyVault 'templates/KeyVault/secrets.bicep' = {
name: 'StorageKeyKeyVault'
params: {
keyVaultName: keyVaultName
keyVaultKey: StorageAppKey
keyVaultValue: storageAccount.listKeys().keys[0].value
}
}
module AppConfigurationKeyValues 'templates/AppConfiguration/singleKeyValue.bicep' = {
name: 'KeyValues'
params: {
appConfigName: appConfigurationName
appConfigKey: 'Storage__AccessKey'
appConfigValue: StorageKeyKeyVault.outputs.keyVaultUri
contentType: 'application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8'
}
}
Conclusion
I hope you enjoyed reading my first blog. If you have any questions or remarks, please leave them down in the comments below! 🙂