Add KeyVault secrets to an Azure App Configuration in Bicep

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.

Albert Einstein, probably..

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
If you don’t have Azure App Configuration set up yet, visit the Azure App Configuration documentation page, or take a look at some examples provided by Microsoft.
 
If you need an introduction to Bicep, please take a look at this introduction on Microsoft Learn, or consider buying my colleague Eduard Keiholz his book about Azure Infrastructure as Code.
Web App to Azure App Configuration to Azure Key Vault

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.

Invalid Azure Key Vault reference

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

				
			
Deployment Output

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! 🙂

Leave a Comment

Your email address will not be published.