Welcome to today’s post.
In today’s post I will be showing how to secure parameters within your Bicep deployments into Azure.
I will also show how to use Azure Key Vault secrets from within a Bicep deployment script.
In a previous post I showed how to deploy Azure resources with modular Bicep template files. I showed how to deploy a web application resource with an SQL database within a logical SQL server resource. The deployment involved specifying an existing logical SQL server resource before deployment of the SQL database.
What if we wanted to deploy a new logical SQL server and specify the credentials within the script? In this case, we would be advised not to specify the credential parameters as defaults as they can be accessed and a breach of security on the logical server resources would be compromised. If we specified the credential parameters without defaults, the engineer or administrator who is deploying the resources from the script would need to know the credentials as they will need to be specified.
However, there is a way we can run the deployment scripts without specifying the credentials in parameter files or from the command line. This involves using the @secure() decorator for the parameter within a template script, and then referencing the value of the parameter from a secure store from within Azure. The secure store I am referring to is the Azure Key Vault. I will show how this is done within a database deployment template. In a previous post I showed how to create and access secrets from an Azure Key Vault.
Securing Credentials within an Azure Key Vault
To store credentials within Azure we can use the Key Vault resource to store credentials securely. To do this we create secrets. The management of secrets is accessible from the Objects menu within the Key Vault resource overview.
Secrets are specified as name/value pairs, with the values secured and hidden when entered. Below are two credential secrets that have been created within a key vault. Note, the Name property is not hidden as it can be specified within a script or resource outside of the key vault, whereas the Secret value property is hidden as this is retrieved when specifying the Name property.
The credential login secret is shown below:
The credential password secret is shown below:
The creator of the key vault and users with Contributor access role can read secrets.
In the next section, we will see how to configure Key Vault access permissions from a Bicep template.
Enabling access permissions of the Key Vault from a Bicep template
Before we can specify a reference to an existing key vault within a Bicep deployment template script, we will need to enable a Resource access option within the Access configuration option under the Settings menu:
Within the Access configuration screen under the Resource access options, check the option Azure Resource Manager for template deployment as shown:
Deploying Secure Azure Resources with a Bicep Template
As I mentioned earlier, the example of where we need to secure the credentials of a Logical SQL Server resource is one scenario where the Azure Key Vault can be used to secure input parameters.
Given we know the following about creation of SQL databases within Azure:
- A Logical SQL Server resource uses credentials.
- An SQL database depends on the Logical SQL Server.
Within a Bicep deployment script, we will perform the following actions:
- Store Logical SQL Server credentials as secrets in Azure Key Vault
- Retrieve Logical SQL Server credentials from an Azure key vault.
- Create a Logical SQL Server.
- Create SQL database within the Logical SQL Server resource.
The dependencies can of the SQL resources be seen from the diagram below, including how they relate to the key vault secrets:
In the next section, I will show how to reference an Azure Key Vault secret from within a Bicep deployment template.
Referencing and Reading Key Vault Secrets from a Bicep Deployment Template
To reference an existing key vault from within a Bicep Deployment script, we use the reference type of the form:
Microsoft.KeyVault/vaults@api_version
We will also need to use the existing keyword in the resource declaration to ensure that the ARM deployment process does not create the key vault.
Below is the code excerpt I use to create a reference to the Key Vault resource:
param pvName string
param pvResourceGroup string
…
resource keyv 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
name: pvName
scope: resourceGroup(pSubscriptionId, pvResourceGroup)
}
After we have created a reference to the Key Vault, we are then ready to access secrets from the key vault. We do this by using the key vault API function, getSecret(). This function takes a parameter which is the key secret name.
Below is a module definition which passes parameters of the SQL Logical Server name, admin login, and admin password.
…
module dbService 'dbTemplate.bicep' = {
name: 'dbService'
params: {
pLocation: pLocation
pSqlServerName: pSqlServer_Name
pSqlServerAdminLogin: keyv.getSecret('dbServerAdminLogin')
pSqlServerAdminPwd: keyv.getSecret('dbServerAdminLoginPassword')
}
}
In the next section, I will show how to combine the above template and module Bicep scripts into a consolidated Bicep template that allows us to pass in both public and secured parameters to provision an Azure hosted web application with a SQL server backend.
A Sample Secure Bicep Template
I will summarize the above with the Bicep scripts I have used in the deployment. I will not show the web application template as that was described in the previous post.
Below is the main Bicep script parameter file:
main.bicepparam
using './main.bicep'
param subscriptionId = '[your subscription id]'
param pvName = '[an existing key vault]'
param pvResourceGroup = '[resource group the key vault is contained in]'
main.bicep
param pSubscriptionId string
param pvName string
param pvResourceGroup string
@description('The resource location')
param pLocation string = resourceGroup().location
@description('storage account name')
param pStorageAccount_Name string = 'storageacc${uniqueString(resourceGroup().id)}'
@description('web application name')
param pWebApp_Name string = 'website${uniqueString(resourceGroup().id)}'
@description('logical sql server name')
param pSqlServer_Name string = 'sqlserver${uniqueString(resourceGroup().id)}'
resource keyv 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
name: pvName
scope: resourceGroup(pSubscriptionId, pvResourceGroup)
}
resource vStorage_Account 'Microsoft.Storage/storageAccounts@2022-05-01' = {
name: pStorageAccount_Name
location: pLocation
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
}
}
module appService 'webAppTemplate.bicep' = {
name: 'appService'
params: {
pLocation: pLocation
pWebAppName: pWebApp_Name
}
}
module dbService 'dbTemplate.bicep' = {
name: 'dbService'
params: {
pLocation: pLocation
pSqlServerName: pSqlServer_Name
pSqlServerAdminLogin: keyv.getSecret('dbServerAdminLogin')
pSqlServerAdminPwd: keyv.getSecret('dbServerAdminLoginPassword')
}
}
output appServiceAppHostName string = appService.outputs.webAppServiceAppHostName
output dbName string = dbService.outputs.dbName
Before I show how the database deployment template script is implemented, I will show how to hide sensitive parameters within a Bicep script. The sensitive parameters I am referring to can include password parameters and connection strings.
To hide input parameters within a Bicep deployment template script, we use the @secure() decorator. The secure decorator prevents the input parameters from showing within an Azure deployment group.
Below is an example where we secure and hide the input login credential in Bicep script:
@secure()
@description('The admin login for SQL server.')
param pSqlServerAdminLogin string
When a deployment is executed from a command line using the Azure CLI or Azure PowerShell, the JSON output for the input parameters only contains the visible input parameters. Secure parameters are not output.
The database deployment script where we receive the secured key vault input parameters from the main template script and use them to create and deploy the Logical SQL Server and SQL database resources is shown below:
dbTemplate.bicep
param pSqlServerName string
param pLocation string
param pSqlDatabaseSkuName string = 'Basic'
param pSqlDatabaseSkuTier string = 'Basic'
@secure()
@description('The admin login for SQL server.')
param pSqlServerAdminLogin string
@secure()
@description('The admin password for SQL server.')
param pSqlServerAdminPwd string
@description('The SQL DB name.')
param pSqlDB_Name string = 'appdb${uniqueString(resourceGroup().id)}'
resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
name: pSqlServerName
location: pLocation
properties: {
administratorLogin: pSqlServerAdminLogin
administratorLoginPassword: pSqlServerAdminPwd
}
}
resource pSQLServer_Name_pSQLDB 'Microsoft.Sql/servers/databases@2022-11-01-preview' = {
name: '${pSqlServerName}/${pSqlDB_Name}'
location: pLocation
sku: {
name: pSqlDatabaseSkuName
tier: pSqlDatabaseSkuTier
}
dependsOn: [
sqlServer
]
}
output dbName string = pSQLServer_Name_pSQLDB.name
Note that we had to specify the SQL database explicitly as a dependency of the Logical SQL Server parent resource. We did this as follows:
dependsOn: [
sqlServer
]
If we had not did this, then the deployment would have resulted in an error as shown:
Opening the error details shows the cause:
Because the deployment of the SQL parent and child resources was executed in parallel, the child resource (the SQL database) was executed before the parent resource (Logical SQL Server) and the deployment of the child resource could not determine the parent resource.
The error resembles the text:
{“code”:”ParentResourceNotFound”,”message”:”Failed to perform ‘write’ on resource(s) of type ‘servers/databases’,
because the parent resource
‘/subscriptions/[subscription id]/resourceGroups/[resource group]/providers/Microsoft.Sql/servers/[logical SQL server name]’ could not be found.”}
A successful deployment group is shown:
In the successful database deployment, we can see the visible input parameters and the two secured input parameters of the logical SQL server that were extracted from the secret values within the Azure Key Vault:
We have seen how to deploy Bicep resources that contain secure input parameters that are contained as secret values within an Azure Key Vault. This has enabled us to keep our deployment secure using Bicep template scripts.
That is all for today’s post.
I hope you have found this post useful and informative.
Andrew Halil is a blogger, author and software developer with expertise of many areas in the information technology industry including full-stack web and native cloud based development, test driven development and Devops.