Today, we will learn how to capture the secrets, configurations or both from resources created using Azure Bicep. This enables the complete automation of infrastructure deployment, eliminating the need for human intervention. Instead of manually traversing individual resources to retrieve valuable information like connection strings, keys, or other configurations, we can streamline the process.
I have published on Github a repository that contains the code from this post.
├── 📁 .github
│ ├── 📁 workflows
│ │ ├── 📄 deploy.yml
├── 📁 src
│ ├── 📁 modules
│ │ ├── 📄 key-vault-secret.bicep (*)
│ │ ├── 📄 key-vault.bicep
│ │ ├── 📄 static-web-app.bicep
│ │ ├── 📄 storage-account.bicep
│ ├── 📄 template.bicep
└── 📄 .gitignore
We need to create several Azure Bicep modules which are individual files containing configurations and resources that can be used by others Azure Bicep files.
Explaining key-vault.bicep file
We need a key-vault.bicep
file, which we will use to create an Azure Key Vault resource.
Two important points:
- You need to create at least one access policy, adding the
objectId
of the service principal you will use to initiate the process. This is necessary because that Service Principal requires specific permissions in Key Vault to create secrets. - If you want, you can also add your own user identifier in order to be able to personally query the secrets. Do it by hardcoding the identifiers in the template or passing parameters. If you manually create the access policy for yourself, the next time you run the Azure Bicep template, it will disappear.
- It’s necessary to extract the name of the Azure Key Vault account with
output name string = keyVault.name
because it will be used later in other modules.
@description('key vault name')
param name string
@description('object id of service principal that creates resources on azure')
@minLength(36)
@maxLength(36)
param objectId string
@description('tenant id')
@minLength(36)
@maxLength(36)
param tenantId string
// resource
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = {
name: name
location: resourceGroup().location
properties: {
sku: {
name: 'standard'
family: 'A'
}
softDeleteRetentionInDays: 7
enableSoftDelete: true
tenantId: tenantId
accessPolicies: [
{
objectId: objectId
tenantId: tenantId
applicationId: ''
permissions: {
keys: ['all']
secrets: ['all']
certificates: ['all']
storage: ['all']
}
}
]
}
}
// outputs
output name string = keyVault.name
Explaining key-vault-secret.bicep file
The following module key-vault-secret.bicep
contains input parameters such as name
which will be used to reference an existing Azure Key Vault account, and a combination of secretName
and secretValue
. Please note that secretName
is surrounded by toUpper()
method, as our team has agreed upon this convention.
Essentially, the module will reference an existing Azure Key Vault account using the existing
keyword and then create a secret within it.
@description('key vault name')
param name string
@description('secret name')
param secretName string
@secure()
@description('secret value')
param secretValue string
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: name
}
resource secret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
parent: keyVault
name: toUpper(secretName)
properties: {
value: secretValue
contentType: 'plain/text'
}
}
Now, this module can be used in other templates or modules whenever you need to store a secret or configuration.
Explaining static-web-app.bicep file: how to capture deployment token and hostname
We created a module named static-web-app.bicep
to create an Static Web App with basic configuration.
Pay attention to the final part of the file.
@description('static web app name')
param name string
@description('key vault name')
param keyVaultName string
@description('deployment random key')
param randomKey string
@description('static web app tier')
@allowed(['Free', 'Standard'])
param skuTier string = 'Free'
@description('static web app tier')
@allowed(['Free', 'Standard'])
param skuName string = 'Free'
// resource
resource staticWebApp 'Microsoft.Web/staticSites@2021-03-01' = {
name: name
location: resourceGroup().location
sku: {
name: skuName
tier: skuTier
}
properties: { }
}
// outputs to key vault
module secret1 'key-vault-secret.bicep' = {
name: 'static-app-${name}-deployment-token-${randomKey}'
params: {
name: keyVaultName
secretName: 'DEPLOYMENT-TOKEN-${toUpper(name)}'
secretValue: staticWebApp.listSecrets().properties.apiKey
}
}
module secret2 'key-vault-secret.bicep' = {
name: 'static-app-${name}-hostname-${randomKey}'
params: {
name: keyVaultName
secretName: 'HOSTNAME-${toUpper(name)}'
secretValue: staticWebApp.properties.defaultHostname
}
}
As you can see, by calling the key-vault-secret.bicep
module from static-web-app.bicep
we can create a deployment that generates a secret within our Azure Key Vault.
module secret1 'key-vault-secret.bicep' = {
name: 'static-app-${name}-deployment-token-${randomKey}'
params: {
name: keyVaultName
secretName: 'DEPLOYMENT-TOKEN-${toUpper(name)}'
secretValue: staticWebApp.listSecrets().properties.apiKey
}
}
module secret2 'key-vault-secret.bicep' = {
name: 'static-app-${name}-hostname-${randomKey}'
params: {
name: keyVaultName
secretName: 'HOSTNAME-${toUpper(name)}'
secretValue: staticWebApp.properties.defaultHostname
}
}
The secretValue
property could change for each resource because the nature of the secret could be different an so on, the way to access to it
The way we populate the secretValue
property can vary depending on the type of resource, of course. In some cases, we will use listSecrets()
, listKeys()
, listCredentials()
methods. Make sure to use the correct mechanism to obtain the value you desire.
To include the static-web-app.bicep
module, you just need a snippet like this in your main Azure Bicep file.
module staticApp './modules/static-web-app.bicep' = {
name: 'static-web-app-${randomKey}'
scope: resourceGroup
params: {
name: 'my-static-web-app-${suffix}'
keyVaultName: keyVault.outputs.name
randomKey: randomKey
skuName: 'Free'
skuTier: 'Free'
}
dependsOn: [ keyVault ]
}
Note that this is is just a portion of the main template.bicep
file
Explaining storage-account.bicep file: how to capture primary and secondary connection string
In this case, we can see the storage-account.bicep
file and how to create an Azure Storage account and how to retrieve the primary and secondary connection strings.
@minLength(3)
@maxLength(24)
@description('storage account name')
param name string
@allowed(['Standard_LRS', 'Standard_GRS', 'Standard_RAGRS', 'Standard_ZRS', 'Premium_LRS'])
param skuName string = 'Standard_LRS'
@allowed(['Standard', 'Premium'])
param skuTier string = 'Standard'
@allowed(['Storage', 'StorageV2', 'BlobStorage', 'FileStorage', 'BlockBlobStorage'])
param kind string = 'StorageV2'
@description('key vault name')
param keyVaultName string
@description('deployment random key')
param randomKey string
// resource
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: name
location: resourceGroup().location
sku: {
name: skuName
tier: skuTier
}
kind: kind
properties: {
publicNetworkAccess: 'Enabled'
}
}
// outputs
var storageAccountKeys = storageAccount.listKeys()
// outputs to key vault
module secret1 './key-vault-secret.bicep' = {
name: 'stg-account-${name}-pri-${randomKey}'
params: {
name: keyVaultName
secretName: 'STORAGE-ACCOUNT-${toUpper(name)}-PRIMARY-CONNECTION-STRING'
secretValue: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccountKeys.keys[0].value};EndpointSuffix=core.windows.net'
}
}
module secret2 './key-vault-secret.bicep' = {
name: 'stg-account-${name}-sec-${randomKey}'
params: {
name: keyVaultName
secretName: 'STORAGE-ACCOUNT-${toUpper(name)}-SECONDARY-CONNECTION-STRING'
secretValue: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccountKeys.keys[1].value};EndpointSuffix=core.windows.net'
}
}
To include the storage-account.bicep
module, you just need a snippet like this in your main Azure Bicep file.
module storageAccount './modules/storage-account.bicep' = {
name: 'storage-account-${randomKey}'
scope: resourceGroup
params: {
name: 'mystorageaccount${suffix}'
kind: 'StorageV2'
skuName: 'Standard_LRS'
skuTier: 'Standard'
keyVaultName: keyVault.outputs.name
randomKey: randomKey
}
dependsOn: [ keyVault ]
}
Explaining template.bicep file
Finally, our main template.bicep file
is responsible for calling each module that we need one by one.
There are several important points you should be aware of to understand how this file works:
- We have a parameter named
randomKey
that we feed from our GitHub Action with the valuegithub.run_id
so that each deployment has a unique identifier, regardless of how many times we launch it. - We use the parameters
objectId
andtenantId
when creating the Azure Key Vault resource to establish access policies. You can find theobjectId
parameter from Enterprise Applications blade - We use a constant named
suffix
with a semi-random value to ensure that the names of the resources we are creating do not collide with existing ones.
targetScope = 'subscription'
@description('The location of the resource group')
param location string
@description('a random key to ensure deployments are unique')
param randomKey string = newGuid()
@description('service principal object id')
param objectId string
@description('tenant id')
param tenantId string
var suffix = '77697'
resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
name: 'my-resource-group-${suffix}'
location: location
}
module keyVault './modules/key-vault.bicep' = {
name: 'key-vault-${randomKey}'
scope: resourceGroup
params: {
name: 'my-key-vault-${suffix}'
objectId: objectId
tenantId: tenantId
}
}
module staticApp './modules/static-web-app.bicep' = {
name: 'static-web-app-${randomKey}'
scope: resourceGroup
params: {
name: 'my-static-web-app-${suffix}'
keyVaultName: keyVault.outputs.name
randomKey: randomKey
skuName: 'Free'
skuTier: 'Free'
}
dependsOn: [ keyVault ]
}
module storageAccount './modules/storage-account.bicep' = {
name: 'storage-account-${randomKey}'
scope: resourceGroup
params: {
name: 'mystorageaccount${suffix}'
kind: 'StorageV2'
skuName: 'Standard_LRS'
skuTier: 'Standard'
keyVaultName: keyVault.outputs.name
randomKey: randomKey
}
dependsOn: [ keyVault ]
}
Does this works?
Yes, we can run the GitHub Action file
After navigating to the Azure Portal, we can find a resource group named my-resource-group-77697
, along with some resources within it.
Furthermore, by accessing the Azure Key Vault resource, we can locate the expected secrets
And… that’s it folks!