Reputation: 995
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.
Upvotes: 4
Views: 16148
Reputation: 51
I found a neat trick! (Well, I think so at least)
I have been annoyed by this for a long time. It is tempting to use guid('SomeName') or uniqueString('SomeName'), but they are not secure.
However, they are secure if you use them with autogenerated secrets from other resources.
// Use the instrumentationKey from Application Insights
var databasePassword = uniqueString(appInsights.properties.InstrumentationKey)
// Or a key from your storage account
var databasePassword = **uniqueString(storageAccount.listKeys().keys[0].value)**
Upvotes: 1
Reputation: 5217
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.Upvotes: 4
Reputation: 45313
For some secrets in Azure, uuid is not good enough, for example, the VM's password
message": "The supplied password must be between 6-72 characters long and must satisfy at least 3 of password complexity requirements from the following: 1) Contains an uppercase character 2) Contains a lowercase character 3) Contains a numeric digit 4) Contains a special character 5) Control characters are not allowed
I did an improvement based on @Whit Waldo 's answer
$ cat deploy.bicep
param guidValue string = newGuid()
var adminPassword = '${toUpper(uniqueString(resourceGroup().id))}-${guidValue}'
output adminPasswordOutput string = adminPassword
$ az group create --name "test" --location "Central US"
$ az deployment group create --name test --resource-group test --template-file deploy.bicep
Upvotes: -1
Reputation: 2978
This is not supported by bicep and there are no such plans based on a request in their repo.
This is not something we would like to take on as generating a cryptographically secure password, with a variety of restrictions based on the resource type is better handled in a deployment script or by using a key vault to generate the password.
Upvotes: 1
Reputation: 58853
uniqueString() is not meant for generating passwords at all, it's for generating names for resources.
As far as I know, there is no purpose-built method to generate a password in a Bicep/ARM template. What we have done is generate passwords with sufficient length and complexity using a password generator, and store those in Azure DevOps variable groups as secrets. Then we pass them in to the template as secure string parameters, so they don't get logged anywhere. We also don't store those generated passwords anywhere else, they are thrown away after generating them and storing them in Azure DevOps.
Upvotes: 3