Azure Bicep security fundamentals
Mark Johnson
December 13, 2022
0 mins readThis post was written by Snyk Ambassador, Mark Johnson (@tazmainiandevil). Get inside access to Snyk by signing up to become a Snyk Ambassador.
Azure Bicep is getting more popular by the day and is rapidly becoming the replacement for Azure Resource Manager (ARM) templates. In this post, I am going to go over some security fundamentals when using Bicep. If you are not familiar with Bicep then I recommend taking a look at the Microsoft Learn documentation to find out more.
Keep secrets out of source control
We all know we want to keep our secrets out of source control but it is very easy to accidentally leave secrets in files, especially when testing out your Bicep configurations locally.
Some ways to avoid committing secrets are:
Pass parameters in via command line.
Use a parameters JSON file that is ignored by source control. For example, add them to your
.gitignore
file if you are using Git.
Secure inputs
Passing in parameters from the outside is one thing, but how do you make sure secrets are secure and not displayed in outputs? Bicep provides an @secure
decorator for String and Object type parameters. For example:
1@secure()
2param adminPassword string
3
4@secure()
5param adminCredentials object
Be careful with outputs
Adding outputs to your Bicep modules is very useful, but there are a few things to be aware of. If you are setting an output that looks like a secret, then Bicep will provide a warning that you are exposing potential secrets. The following output for a connection string to a storage account would output such a warning:
1output connection string = 'DefaultEndpointsProtocol=https;AccountName=${storageaccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
However, if the value was added to a variable before being assigned to the output, then no warning would be shown and would be easy to miss.
1var connectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageaccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
2
3output connection string = connectionString
Now, let's see what happens if a storage account resource is deployed to Azure using the following configuration:
1deploy.bicep
2param location string = resourceGroup().location
3param tags object = {}
4param storageName string = 'stsecureteststore'
5param sku string = 'Standard_LRS'
6
7module storageModule 'modules/storage.bicep' = {
8 name: 'StorageDeploy'
9 params: {
10 location: location
11 storageName: storageName
12 tags: tags
13 sku: sku
14 }
15}
16
17modules/storage.bicep
18
19@description('The storage account name')
20@minLength(3)
21@maxLength(24)
22param storageName string
23@description('The storage account location')
24param location string
25@description('The tags for the storage account')
26param tags object
27@description('The storage account sku')
28@allowed([ 'Standard_LRS', 'Standard_GRS', 'Standard_GZRS', 'Standard_RAGRS', 'Standard_RAGZRS', 'Standard_ZRS', 'Premium_LRS', 'Premium_ZRS' ])
29param sku string = 'Standard_LRS'
30@description('The access tier for the blob services')
31@allowed([ 'Hot', 'Cool' ])
32param accessTier string = 'Hot'
33@description('Allow public access to blobs')
34param allowBlobPublicAccess bool = false
35
36resource storageaccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
37 name: storageName
38 location: location
39 kind: 'StorageV2'
40 tags: tags
41 sku: {
42 name: sku
43 }
44 properties: {
45 supportsHttpsTrafficOnly: true
46 minimumTlsVersion: 'TLS1_2'
47 accessTier: accessTier
48 allowBlobPublicAccess: allowBlobPublicAccess
49 }
50}
51
52var connectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageaccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
53output connection string = connectionString
Any outputs defined in Bicep can be seen as under Deployments for the resource group the resources have been deployed to:
Looking at the StorageDeploy outputs, we see that the connection is shown with the account key in plain text:
This means anyone with access to view the resources in the Azure Portal can see these outputs. To maintain a good security posture, it is recommended to not return secrets as outputs in Bicep.
Hopefully, Bicep will support the use of the @secure
decorator for outputs in the future to make returning secrets safe and secure.
Secrets from resources
If returning secrets from Bicep is a problem, then how do you get secrets from one module to another? One option is to access an existing resource by using the existing keyword. For example:
1param storageName string
2
3resource storageaccount 'Microsoft.Storage/storageAccounts@2022-05-01' existing = {
4 name: storageName
5}
6
7var connectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
This connection string could then be used as an input for another resource.
Secrets from Key Vault
Getting existing resources is one way of getting secrets but there is also support for using a Key Vault to retrieve secrets.
Note: Make sure that the Key Vault Access Configuration allows access via "Azure Resource Manager for template deployment"
Key Vaults are accessed in the same way as in the previous section, by using the existing keyword. One caveat to note, however, is that getSecret
method can only be used when assigning to a module parameter with the @secure
decorator:
1deploy.bicep
2param location string = resourceGroup().location
3param tags object
4param sqlServerName string
5param keyVaultName string
6param keyVaultResourceGroupName string
7param subscriptionId string = subscription().subscriptionId
8
9resource vaultResource 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
10 name: keyVaultName
11 scope: resourceGroup(subscriptionId, keyVaultResourceGroupName )
12}
13
14module sqlModule 'modules/sql.bicep' = {
15 name: 'SqlDeploy'
16 params: {
17 location: location
18 tags: tags
19 sqlServerName: sqlServerName
20 administratorLogin: vaultResource.getSecret('sqlUser')
21 administratorLoginPassword: vaultResource.getSecret('sqlPassword')
22 }
23}
24
25modules/sql.bicep
26@description('The resource location')
27param location string
28@description('The tags for the resources')
29param tags object
30@description('The name for the SQL Server')
31param sqlServerName string
32@secure()
33@description('The SQL Administrator Login')
34param administratorLogin string
35@secure()
36@description('The SQL Administrator password')
37param administratorLoginPassword string
38
39resource sqlServerResource 'Microsoft.Sql/servers@2022-05-01-preview' = {
40 name: sqlServerName
41 location: location
42 tags:tags
43 properties: {
44 administratorLogin: administratorLogin
45 administratorLoginPassword: administratorLoginPassword
46 }
47}
Security scanning Bicep
Scanning of infrastructure as code (IaC) is becoming quite popular, and it is good to see that there is interest in finding security issues as early as possible. Snyk has a free CLI that can be used to perform IaC scans locally against security and compliance standards. While it does not directly support the Bicep format, it does support scanning of ARM templates that Bicep compiles down to.
To compile Bicep to ARM, you need to have the Bicep CLI installed, and to get started with the Snyk CLI create a free account and then install Snyk CLI using npm. If you have Node.js installed locally, you can install it by running:
1npm install snyk@latest -g
Once installed and setup you can then run the command:
1az bicep build -f {file_name}.bicep
This will produce a JSON file with the same name as the Bicep file and then you can run the Snyk scan with the command:
1snyk iac test {file_name}.json
Final thoughts
Security is something we all have to think about. While it's a constantly moving target, the more we learn the more we can do to help secure our resources. I hope this post has been informative and provided some insights to securing your Bicep configurations.
IaC security designed for devs
Snyk secures your infrastructure as code from SDLC to runtime in the cloud with a unified policy as code engine so every team can develop, deploy, and operate safely.