Use Bicep to manage secrets

Deployments often require secrets to be stored and propagated securely throughout your Azure environment. Bicep and Azure provide many features to assist you with managing secrets in your deployments.

Avoid secrets where you can

It is possible to avoid using secrets altogether in many situations. Many Azure resources enable managed identities to authenticate and be authorized to access other resources within Azure and without you needing to handle or manage any credentials. Additionally, some Azure services can generate HTTPS certificates for you automatically, sparing you from handling certificates and private keys. Use managed identities and service-managed certificates wherever possible.

Use secure parameters

When you need to provide secrets to your Bicep deployments as parameters, use the @secure() decorator. When you mark a parameter as secure, Azure Resource Manager avoids logging the value or displaying it in the Azure portal, the Azure CLI, or Azure PowerShell.

Avoid outputs for secrets

Don't use Bicep outputs for secure data. Outputs are logged to the deployment history, and anyone with access to the deployment can view the values of a deployment's outputs.

If you need to generate a secret within a Bicep deployment and make it available to the caller or to other resources, consider one of the following approaches.

Look up secrets dynamically

Sometimes, you need to access a secret from one resource to configure another one. For example, you might have created a storage account in another deployment and need to access its primary key to configure an Azure Functions app. You can use the existing keyword to obtain a strongly typed reference to the pre-created storage account, and then use the storage account's listKeys() method to create a connection string with the primary key.

The following example is part of a larger example. For a Bicep file that you can deploy, see the complete file.

param location string = resourceGroup().location
param storageAccountName string
param functionAppName string = 'fn-${uniqueString(resourceGroup().id)}'

var appServicePlanName = 'MyPlan'
var applicationInsightsName = 'MyApplicationInsights'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = {
  name: storageAccountName
}

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

resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp'
  properties: {
    httpsOnly: true
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
          value: applicationInsights.properties.InstrumentationKey
        }
        {
          name: 'AzureWebJobsStorage'
          value: storageAccountConnectionString
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~3'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'dotnet'
        }
        {
          name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
          value: storageAccountConnectionString
        }
      ]
    }
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: 'Y1' 
    tier: 'Dynamic'
  }
}

resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
  name: applicationInsightsName
  location: location
  kind: 'web'
  properties: { 
    Application_Type: 'web'
    publicNetworkAccessForIngestion: 'Enabled'
    publicNetworkAccessForQuery: 'Enabled'
  }
}

Taking this approach can help you to avoid passing secrets into or out of your Bicep file and also to store secrets in a key vault.

Use Key Vault

Azure Key Vault is designed to store and manage secure data. Use a key vault to manage your secrets, certificates, keys, and other data that needs to be protected and shared.

You can use Bicep to create and manage vaults and secrets. Define your vaults by creating a resource with the type Microsoft.KeyVault/vaults.

When you create a vault, you need to determine who and what can access its data. If you plan to read the vault's secrets from within a Bicep file, set the enabledForTemplateDeployment property to true.

Add secrets to a key vault

Secrets are a child resource and can be created by using the type Microsoft.KeyVault/vaults/secrets. The following example demonstrates how to create a vault and a secret.

The following example is part of a larger example. For a Bicep file that you can deploy, see the complete file.

param location string = resourceGroup().location
param keyVaultName string = 'mykv${uniqueString(resourceGroup().id)}'

resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: keyVaultName
  location: location
  properties: {
    enabledForTemplateDeployment: true
    tenantId: tenant().tenantId
    accessPolicies: [
    ]
    sku: {
      name: 'standard'
      family: 'A'
    }
  }
}

resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
  parent: keyVault
  name: 'MySecretName'
  properties: {
    value: 'MyVerySecretValue'
  }
}

Tip

When you use automated deployment pipelines, it can sometimes be challenging to determine how to bootstrap key vault secrets for your deployments. For example, if you've been provided with an API key to use when communicating with an external API, then the secret needs to be added to a vault before it can be used in your deployments.

When you work with secrets that come from a third party, you might need to manually add them to your vault before you can reference them for all subsequent uses.

Use a key vault with modules

When you use Bicep modules, you can provide secure parameters by using the getSecret function.

You can also reference a key vault defined in another resource group by using the existing and scope keywords together. In the following example, the Bicep file is deployed to a resource group named Networking. The value for the module's parameter mySecret is defined in a key vault named contosonetworkingsecrets, which is located in the Secrets resource group:

resource networkingSecretsKeyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
  scope: resourceGroup('Secrets')
  name: 'contosonetworkingsecrets'
}

module exampleModule 'module.bicep' = {
  name: 'exampleModule'
  params: {
    mySecret: networkingSecretsKeyVault.getSecret('mySecret')
  }
}

Use a key vault in a .bicepparam file

When you use .bicepparam file format, you can provide secure values to parameters by using the getSecret function.

Reference the key vault by providing the subscription ID, resource group name, and key vault name. You can get the value of the secret by providing the secret name. You can optionally provide the secret version; the latest version is used if you don't.

using './main.bicep'

param secureUserName = az.getSecret('<subscriptionId>', '<resourceGroupName>', '<keyVaultName>', '<secretName>', '<secretVersion>')
param securePassword = az.getSecret('<subscriptionId>', '<resourceGroupName>', '<keyVaultName>', '<secretName>')

Work with secrets in pipelines

The following best practices can help you to handle your secrets with caution when you use a pipeline to deploy your Azure resources:

  • Avoid storing secrets in your code repository. For example, don't add secrets to parameters files or to pipeline definition YAML files.
  • In GitHub Actions, use encrypted secrets to store secure data. Use secret scanning to detect any accidental commits of secrets.
  • In Azure Pipelines, use secret variables to store secure data.