Manage secrets by using Bicep

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

In many situations, it's possible to avoid using secrets at all. Many Azure resources support managed identities, which enable them to authenticate and be authorized to access other resources within Azure, without you needing to handle or manage any credentials. Additionally, some Azure services can generate HTTPS certificates for you automatically, avoiding you 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, 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 using one of the following approaches.

Look up secrets dynamically

Sometimes, you need to access a secret from one resource to configure another resource.

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'
  }
}

By using this approach, you avoid passing secrets into or out of your Bicep file.

You can also use this approach 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 create and manage vaults and secrets by using Bicep. 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 may need to manually add them to your vault, and then you can reference the secret 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 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 KeyVault 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. If you don't provide the secret version, the latest version is used.

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

When you deploy your Azure resources by using a pipeline, you need to take care to handle your secrets appropriately.

  • Avoid storing secrets in your code repository. For example, don't add secrets to parameter files, or to your 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.