Azure Bicep security fundamentals

SnykSec - Dec 20 '22 - - Dev Community

This 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 theMicrosoft 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 commiting 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:

@secure()
param adminPassword string

@secure()
param adminCredentials object
Enter fullscreen mode Exit fullscreen mode

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:

output connection string = 'DefaultEndpointsProtocol=https;AccountName=${storageaccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
Enter fullscreen mode Exit fullscreen mode

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.

var connectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageaccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'

output connection string = connectionString
Enter fullscreen mode Exit fullscreen mode

Now, let’s see what happens if a storage account resource is deployed to Azure using the following configuration:

deploy.bicep
param location string = resourceGroup().location
param tags object = {}
param storageName string = 'stsecureteststore'
param sku string = 'Standard_LRS'

module storageModule 'modules/storage.bicep' = {
  name: 'StorageDeploy'
  params: {
    location: location
    storageName: storageName
    tags: tags
    sku: sku
  }
}

modules/storage.bicep

@description('The storage account name')
@minLength(3)
@maxLength(24)
param storageName string
@description('The storage account location')
param location string
@description('The tags for the storage account')
param tags object
@description('The storage account sku') 
@allowed(['Standard_LRS', 'Standard_GRS', 'Standard_GZRS', 'Standard_RAGRS', 'Standard_RAGZRS', 'Standard_ZRS', 'Premium_LRS', 'Premium_ZRS'])
param sku string = 'Standard_LRS'
@description('The access tier for the blob services') 
@allowed(['Hot', 'Cool']) 
param accessTier string = 'Hot' 
@description('Allow public access to blobs') 
param allowBlobPublicAccess bool = false 

resource storageaccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
  name: storageName
  location: location
  kind: 'StorageV2'
  tags: tags
  sku: {
    name: sku
  }
  properties: {
    supportsHttpsTrafficOnly: true
    minimumTlsVersion: 'TLS1_2'
    accessTier: accessTier
    allowBlobPublicAccess: allowBlobPublicAccess
  }
}

var connectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageaccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
output connection string = connectionString
Enter fullscreen mode Exit fullscreen mode

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:

param storageName string

resource storageaccount 'Microsoft.Storage/storageAccounts@2022-05-01' existing = {
  name: storageName  
}

var connectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageaccount.id, storageaccount.apiVersion).keys[0].value}'
Enter fullscreen mode Exit fullscreen mode

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:

deploy.bicep
param location string = resourceGroup().location
param tags object
param sqlServerName string
param keyVaultName string
param keyVaultResourceGroupName string
param subscriptionId string = subscription().subscriptionId

resource vaultResource 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
  name: keyVaultName 
  scope: resourceGroup(subscriptionId, keyVaultResourceGroupName )
}

module sqlModule 'modules/sql.bicep' = {
  name: 'SqlDeploy'
  params: {
    location: location
    tags: tags
    sqlServerName: sqlServerName
    administratorLogin: vaultResource.getSecret('sqlUser')
    administratorLoginPassword: vaultResource.getSecret('sqlPassword')
  }  
}

modules/sql.bicep
@description('The resource location')
param location string
@description('The tags for the resources')
param tags object
@description('The name for the SQL Server')
param sqlServerName string
@secure()
@description('The SQL Administrator Login')
param administratorLogin string
@secure()
@description('The SQL Administrator password')
param administratorLoginPassword string

resource sqlServerResource 'Microsoft.Sql/servers@2022-05-01-preview' = {
  name: sqlServerName
  location: location
  tags:tags
  properties: {
    administratorLogin: administratorLogin
    administratorLoginPassword: administratorLoginPassword
  }
}
Enter fullscreen mode Exit fullscreen mode

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 theBicep CLI installed, and to get started with the Snyk CLIcreate a free account and then install Snyk CLI using npm. If you have Node.js installed locally, you can install it by running:

npm install snyk@latest -g
Enter fullscreen mode Exit fullscreen mode

Once installed and setup you can then run the command:

az bicep build -f {file_name}.bicep
Enter fullscreen mode Exit fullscreen mode

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:

snyk iac test {file_name}.json
Enter fullscreen mode Exit fullscreen mode

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.

Snyk cloud security

Secure your applications from code to cloud and back with Snyk.

Schedule a demo

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .