The existing keyword in Bicep
I learned to write ARM templates fairly fast. Mainly because I already had some understanding of what’s going on in the background, I was able to understand the mechanics of ARM templates as well. Now although it still does help a lot to have some understanding of Azure, not specifically the Azure Resource Manager, Bicep is a different thing. I already covered some of the major differences in an earlier post so let’s get on with and explore some hidden gems.
Writing real-world templates
I’m always writing hobby projects to explore new technologies and features. The ‘building a board game’ is one of those projects (still an ongoing process). I recently launched a new project together with some of my 4Dotnet colleagues allowing us to play in an actual cloud environment (Azure) and have fun with new technologies and features and obviously, Bicep is one of them.
For this project, we require Azure Functions to handle some events, do some asynchronous magic, and some background clean-up. This project is now the subject of this post and because of a very specific feature that lives within Bicep.
The scenario
I’m talking about a hidden gem called being the existing
keyword. My scenario and I often use this scenario, is to have a deploy-time Key Vault and one or more run-time Key Vaults in Azure for every let’s call it ‘Domain’. These run-time Key Vaults obviously contain the secrets I need in order to run the app, connection strings, access keys, and so on. However, some secrets may need to be configured up front somewhere.
One example is when you want to communicate with an external API, you may need to store an API key somewhere. Or when you want to use the Azure DevOps API, you may need to store a PAT token somewhere. In these scenario’s I like to use a deploy-time Key Vault.
These Key Vaults are configured to let developers and operations only show a list of available secrets and to set a secret, but not to fetch (get) it. This allows developers to (for example) generate a PAT token and store that in the deploy-time Key Vault.
Working on the template
I finished the Azure Function and now focussed on the Bicep template and I was wondering how to get secrets from one Key Vault to a fresh and shiny brand new Key Vault that my Bicep template just provisioned.
This is where the existing
keyword turns out to be a lifesaver. With the existing keyword, you may reference (and access) properties of resources outside of your template. In other words, you assume a resource is already there, you create a resource in your Bicep template with the existing
keyword and you are now able to access that Key Vault. Brilliant!
Show me the money!
Before we dig into the template it’s worth telling you that I used Azure DevOps to maintain this project. So I opened the project in Azure DevOps and created a Service Connection (Azure Resource Manager) with scope ‘Subscription’ to my Azure environment. This is because I want to create a resource group from within my pipeline. In case your resource group is already there, you can also select the resource group when creating your service connection to limit the permissions for the SPN created when generating the Service Connection.
Then I went to the Key Vault, searched for the SPN created for the Service Connection and granted ’list’, and ‘get’ permission to the secrets inside my deploy-time Key Vault.
Now I opened VS Code and started on my Bicep template. Remember there is this very awesome VS Code extension that allows you to write Bicep templates at record-breaking speed.
So first thing is that I needed to reference the deploy-time Key Vault. Unfortunately, this Key Vault lives in a different resource group. Luckily there is a scope property allowing you to reference Key Vaults in different Resource Groups and even different subscriptions if you want to.
resource deployTimeKeyVault 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = {
name: 'DeployTimeKeyVault'
scope: resourceGroup('DeployTime')
}
So above is the reference to my existing deploy-time Key Vault. Notice the existing
keyword at the end of the resource type and API version. You can also see that I use the scope property to tell that this resource lives in a different resource group.
Now it’s time to create my very brand new Key Vault for my run-time environment:
resource keyVault 'Microsoft.KeyVault/vaults@2021-04-01-preview' = {
name: keyVaultName
location: resourceGroup().location
properties: {
tenantId: subscription().tenantId
sku: {
family: 'A'
name: sku
}
accessPolicies: []
enableSoftDelete: false
}
}
Nothing special here, it is just the creation of a new Key Vault. Now for this example, I create a SQL Server with an admin password and store the connection string as a secret in the run-time Key Vault. Although I know you could also use Managed Identities to grant access to the SQL Server I think this is an example that readers of this post are familiar with.
So I created a new Bicep file ‘SQL/servers.bicep’ that looks like so:
param systemName string = 'your-system-name'
@allowed([
'dev'
'test'
'acc'
'prod'
])
param environmentName string = 'prod'
param azureRegion string = 'weu'
@secure()
param sqlServerPassword string = newGuid()
var sqlServerName = '${systemName}-${environmentName}-${azureRegion}-sqlserver'
var adminName = '${systemName}${environmentName}${azureRegion}-admin'
var connectionString = 'DATA SOURCE=tcp:${sqlServerName}${environment().suffixes.sqlServerHostname},1433;USER ID=${adminName};PASSWORD=${sqlServerPassword};INITIAL CATALOG=${databaseName};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
resource sqlServer 'Microsoft.Sql/servers@2021-02-01-preview' = {
name: sqlServerName
location: resourceGroup().location
properties: {
administratorLogin: adminName
administratorLoginPassword: sqlServerPassword
}
}
output connectionString string = connectionString
Note that this template accepts a couple of parameters (systemName, environmentName, azureRegion, and sqlServerPassword). Also, note that the sqlServerPassword has the @secure()
decorator in front of it. This means that your parameter must remain recure and should not (for example) appear in any (log) output or something. This @secure()
is mandatory. If you want to pass a parameter value in from a Key Vault secret, this parameter must have the @secure()
decorator.
Now let’s go back to the main template, and add a module so our template deploys the SQL Server.
module sqlServerModule 'Sql/servers.bicep' = {
name: 'sqlServerModule'
params: {
systemName: 'website-x'
environmentName: 'test'
azureRegion: 'weu' // This is not an actual Azure Region
sqlServerPassword: deployTimeKeyVault.getSecret('sql-server-password', 'verion')
}
}
You can see that I pass in all the parameters, including the sqlServerPassword
, that get its value from a Key Vault secret.
Now once this module is completed successfully, my resource group will contain a Key Vault and a SQL Server with an admin password configured in my deploy-time Key Vault. If you take a careful look at the bottom of ‘SQL/servers.bicep’, you’ll see an output
variable, which is the connection string that I need to use at run-time. You can use that output variable to store the connection string as a secret in the newly created Key Vault at the beginning of our template. So this is the run-time Key Vault, not the deploy-time Key Vault:
resource sqlServerConnectionString 'Microsoft.KeyVault/vaults/secrets@2021-04-01-preview' = {
dependsOn: [
keyVault
sqlServerModule
]
name: '${keyVault.name}/SqlConnectionString'
properties: {
value: sqlServerModule.outputs.connectionString
}
}
The name of the secret is SqlConnectionString
but it must be prefixed with the name of our KeyVault. With the dependsOn, you tell the system to wait for both the SQL Server and the Key Vault to complete. Then your new secret will be created, with a value that comes from the output of a different template.
An additional scenario
A different example but again very powerful in my opinion is to use this very same existing
keyword on a Key Vault for the use of certificates. Although more and more services in Azure now have native support for SSL certificates taking away the stress of renewing and maintenance, you still may need to sometimes.
This example shows how to import a certificate from a deploy-time Key Vault into an app service plan. Again, you need to make sure that the SPN (Service Connection) has enough permissions to access the certificates in the deploy-time Key Vault.
resource certificate 'Microsoft.Web/certificates@2021-01-01' = {
name: 'certificateModule'
location: resourceGroup().location
properties: {
keyVaultId: deployTimeKeyVault.id
keyVaultSecretName: 'certificate-name'
serverFarmId: appServicePlanModule.outputs.id
}
}
After you imported the certificate, you can use it when creating a new hostname biding on your web app like so:
resource hostNameBinding 'Microsoft.Web/sites/hostNameBindings@2021-01-01' = {
dependsOn: [
certificate
]
name: '${webAppName}/www.your-domain-name.com'
properties: {
siteName: webAppName
hostNameType: 'Verified'
sslState: 'SniEnabled'
thumbprint: certificate.properties.thumbprint
}
}