I'm coming from a Terraform background and AWS. Now I'm using Bicep with Azure, so please bear with me :)
In Terraform, we create random passwords with the random_password resource. We then stored this as a value in the AWS Systems Manager Parameter Store. This allowed us to have secure (enough...) passwords, which got created and stored in some secure database without us having to enter or even know the password. If somebody would need to know the password, it would get logged. Lovely ;)
Now...
How do I do something like this with Bicep? I'm only finding the uniqueString() function. But this only creates 13 character long random strings and also doesn't have any "special" characters like !@#$%&*()-_=+[]{}<>:? and such.
For quite obvious reasons, I don't want to have some sort of statement in my code, which sets the secret to some clearly readable value. Which is why we used random_password in Terraform.
What's the right approach to solve this in Bicep?
I found the blog post "Automatically generate a password for an Azure SQL database with ARM template" by Vivien Chevallier, but that isn't good, IMO. To circumvent the short comings of the uniqueString() function and make it comply to the password complexity rules, the person adds a constand prefix of "P" and suffix of "x!". This reduces the quality of the password, as there now 3 known characters. Out of 16.
The intent in Bicep is that one creates fully idempotent templates so that you should receive the same output every time you attempt to deploy anything in Bicep. As such, randomString and newGuid both accept parameters you can use to seed the result, but you'll always get the same result for the new values you put in.
For the reasons above, you're encouraged to provide your own generated password to kick off template deployment externally so there's nothing about the Bicep template that's changing from one deployment to another. On top of that, you're not really encouraged to expose the password in an output value since those are all logged indefinitely, so I highly recommend writing out the value to a Key Vault secret as in the following
@secure() //Prevents it from being logged, but also removes it from output
param password string = newGuid() //Can only be used as the default value for a param
@description('The name of the Key Vault to save the secret to')
param KeyVaultName string
@description('The name of the secret in Key Vault')
param KeyVaultSecretName string
//Save as Key Vault secret
resource KeyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
  name: KeyVaultName
}
resource KVSecret 'Microsoft.KeyVault/vaults/secrets@2021-06-01-preview' = {
  name: replace(replace(SecretName, '.', '-'), ' ', '-')
  parent: KeyVault
  properties: {
    contentType: 'text/plain'
    attributes: {
      enabled: true
    }
    value: password
  }
}
output PasswordSecretUri string = KVSecret.properties.secretUri
And then you can use the GUID output as your password for your use-case knowing that it will be different every time you run the Bicep deployment.
Edit: If you simply want a more complex password, I suggest using a PowerShell deployment script to generate a password that matches your complexity requirements. Here's a small snippet that can do just that, adapted from here to include a shuffle function:
function Shuffle-String([string]$inputString) {
    $charArray = $inputString.ToCharArray()
    $rng = New-Object System.Random
    $remainingChars = $charArray.length
    while ($remainingChars -gt 1) {
        $remainingChars--
        $charIndex = $rng.Next($remainingChars + 1)
        $value = $charArray[$charIndex]
        $charArray[$charIndex] = $charArray[$remainingChars]
        $charArray[$remainingChars] = $value
    }
    return -join $charArray
}
function Create-Password() {
  $TokenSet = @{
        U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        L = [Char[]]'abcdefghijklmnopqrstuvwxyz'
        N = [Char[]]'0123456789'
        S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~'
  }
 
  $Upper = Get-Random -Count 5 -InputObject $TokenSet.U
  $Lower = Get-Random -Count 5 -InputObject $TokenSet.L
  $Number = Get-Random -Count 5 -InputObject $TokenSet.N
  $Special = Get-Random -Count 5 -InputObject $TokenSet.S
  $Combined = ($Upper + $Lower + $Number + $Special) -join ''
  return Shuffle-String $Combined
}
Call with 'Create-Password' to get an output like 'WT{"v3)ziD21H48Lw_q'.
For completeness, here's what your deployment script module would then look like:
param Location string = resourceGroup().location
param UtcNow string = utcNow()
param DeploymentScriptName string = newGuid()
var ScriptContent = '''
function Shuffle-String([string]$inputString) {
  $charArray = $inputString.ToCharArray()
  $rng = New-Object System.Random
  $remainingChars = $charArray.length
  while ($remainingChars -gt 1) {
      $remainingChars--
      $charIndex = $rng.Next($remainingChars + 1)
      $value = $charArray[$charIndex]
      $charArray[$charIndex] = $charArray[$remainingChars]
      $charArray[$remainingChars] = $value
  }
  return -join $charArray
}
function Create-Password() {
$TokenSet = @{
      U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
      L = [Char[]]'abcdefghijklmnopqrstuvwxyz'
      N = [Char[]]'0123456789'
      S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~'
}
$Upper = Get-Random -Count 5 -InputObject $TokenSet.U
$Lower = Get-Random -Count 5 -InputObject $TokenSet.L
$Number = Get-Random -Count 5 -InputObject $TokenSet.N
$Special = Get-Random -Count 5 -InputObject $TokenSet.S
$Combined = ($Upper + $Lower + $Number + $Special) -join ''
return Shuffle-String $Combined
}
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['password'] = Create-Password
'''
resource DeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
  name: DeploymentScriptName
  kind: 'AzurePowerShell'
  location: Location
  identity: {
  }
  properties: {
    forceUpdateTag: UtcNow
    azPowerShellVersion: '6.4'
    scriptContent: ScriptContent
    timeout: 'PT15M'
    cleanupPreference: 'Always'
    retentionInterval: 'PT1H'
    arguments: ''
  }
}
And as illustrated above, I recommend that you take the password value from the script output (in variable DeploymentScript.properties.outputs.password) and pass it directly into module (e.g. from within this module as opposed to passing it in an output parameter to a parent module that subsequently calls another) that will save it as a secret in Azure Key Vault, applying the @secure() attribute to the password parameter so it isn't logged.
Two important takeaways:
$Combined | Get-Random -Shuffle as they're not available.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With