4

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.

Alexander Skwar
  • 875
  • 1
  • 9
  • 20
  • Could you generate the passwords using a script? Then set them temporarily in your properties file when deploying the template? – Pablo Jomer Jul 01 '21 at 04:31
  • UUID is never a good enough password as it is not cryptographically random, hence you should not use uniqueString() to generate a password. See https://security.stackexchange.com/questions/157270/using-v4-uuid-for-authentication – Santhos Jun 09 '23 at 22:22

4 Answers4

3

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.

juunas
  • 54,244
  • 13
  • 113
  • 149
  • Hm. That's extremely disappointing. So to setup the complete infrastructure, Bicep isn't enough? Do I get this right? Have to rely on some external tool to create the random string? Reg. "Azure DevOps variable groups" - you mean "az pipelines variable-group" (just asking, as I'm still new to this and thus looking for the right way to go)? – Alexander Skwar Jun 21 '21 at 08:24
  • Yeah I mean those groups that the command you mentioned manages. – juunas Jun 21 '21 at 13:02
  • 1
    Take a look at key vault parameter references. The best approach would be to have a vault in azure with your passwords (hosted, generated and rotated separately) and use values from that vault in deployments. With passwords I feel it's usually a chinken and egg problem :) – Miq Jun 21 '21 at 20:12
3

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:

  • Azure Powershell deployment scripts run Azure Powershell which is equivalent to Powershell 5.1. As such, you cannot use more recent shuffle functions like $Combined | Get-Random -Shuffle as they're not available.
  • You don't mention a requirement for a cryptographically secure password, but let me address the lengthy thread under my post. I'm not aware of a cmdlet built into Powershell that is capable of generating cryptographically secure strings. Yes, locally, you could invoke the functionality from .NET via something like 'System.Security.Cryptography.RandomNumberGenerator', but a Bicep deployment script runs as a containerized instance without elevated privileges, so unless they happen to have the .NET SDK available in the container (which hasn't been my own experience), you're not going to be able to achieve this level of password-generating security.
Whit Waldo
  • 4,806
  • 4
  • 48
  • 70
  • UUID is never a good enough password as it is not cryptographically random. See https://security.stackexchange.com/questions/157270/using-v4-uuid-for-authentication – Santhos Jun 09 '23 at 22:24
  • I never claimed a GUID was cryptographically random. I answered the question as asked of how to ensure that a different value is created each time the Bicep template is run that didn't require the use of uniqueString(). I assume that if the author wants to create a PowerShell-based cryptographically secure string, they'll ask as much in their question. – Whit Waldo Jun 10 '23 at 05:42
  • The OP clearly asks for generating password and even mentions/cares about quality of the password. If you ask to rent a car, do you also get a car with broken engine? Is the answer of the car rental that they expect the customer to ask for a car with working engine? Giving such answers only lead to security misunderstandings/flaws. How about you update the answer with that information. I think it would make it more complete. – Santhos Jun 11 '23 at 16:14
  • The OP says that he was comfortable with the output of Terraform's not-cryptographically-secure `random_password` function, but he wanted a value longer than the 13-character output of Bicep's `uniqueString` function. As such, I suggested the longer output `newGuid` function per the ask. – Whit Waldo Jun 11 '23 at 18:05
  • https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password ... "This resource does use a cryptographic random number generator." - Exactly taken from the docs. – Santhos Jun 13 '23 at 09:00
  • Yes... per the OP's question: "In Terraform, we create random passwords with the random_password resource...This allowed us to have secure (enough...) passwords" Thus, if that's secure enough for them, my answer is sufficient to answer the rest of the ask. – Whit Waldo Jun 14 '23 at 11:18
  • 1
    I've augmented my answer to address the password complexity requirements and speak to the difficulty (impossibility?) of generating a cryptographically secure password via an ARM deployment script. – Whit Waldo Jun 14 '23 at 11:56
1

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.

DreadedFrost
  • 2,602
  • 1
  • 11
  • 29
-1

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
BMW
  • 42,880
  • 12
  • 99
  • 116
  • UUID is never a good enough password as it is not cryptographically random. See https://security.stackexchange.com/questions/157270/using-v4-uuid-for-authentication – Santhos Jun 09 '23 at 22:20