0

In my Bicep file, I'm using an Azure KeyVault resource like so:

resource kv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = {
  name: kvName
  scope: resourceGroup(subscriptionId, resourceGroup().name)
}

module db 'modules/database.bicep' = if (dbIsEnabled) {
  name: 'db'
  scope: resourceGroup()
  params: {
    location: location
    namePrefix: namePrefix
    regionSuffix: regionSuffix
    administratorLogin: dbAdminUsername
    administratorLoginPassword: kv.getSecret('dbAdminPassword')
    edition: dbEdition
  }
}

This works fine (I guess...), but only the first time.

$ az deployment group create --verbose -c --mode Complete -g "$rgName" -f shr.bicep -p "@$paramFile"
{'properties': {'template': <azure.cli.command_modules.resource.custom.JsonCTemplate object at 0x7fbc0079aa90>, 'parameters': {'subscriptionId': {'value': '20d2016b-1234-1234-1234-be72462e1234'}, 'kvName': {'value': 'shrKvTest0830'}, 'tenantPrefix': {'value': 'adv'}, 'environmentSuffix': {'value': 'demo'}, 'regionSuffix': {'value': 'useast'}, 'dbIsEnabled': {'value': True}, 'dbEdition': {'value': 'Premium'}, 'dbAdminUsername': {'value': 'adm'}}, 'mode': 'Complete', 'whatIfSettings': {'resultFormat': 'FullResourcePayloads'}}}
Noneunning ..
Note: The result may contain false positive predictions (noise).
You can help us improve the accuracy of the result by opening an issue here: https://aka.ms/WhatIfIssues.

Resource and property changes are indicated with these symbols:
  - Delete
  + Create

The deployment will update the following scope:

Scope: /subscriptions/20d2016b-1234-1234-1234-be72462e1234/resourceGroups/shrDevopsTesting123

  - Microsoft.KeyVault/vaults/shrKvTest0830

      id:       "/subscriptions/20d2016b-1234-1234-1234-be72462e1234/resourceGroups/shrDevopsTesting123/providers/Microsoft.KeyVault/vaults/shrKvTest0830"
      location: "westeurope"
      name:     "shrKvTest0830"
      type:     "Microsoft.KeyVault/vaults"

  + Microsoft.Sql/servers/adv-demo-db-srv-useast [2021-02-01-preview]

      apiVersion:                            "2021-02-01-preview"
      id:                                    "/subscriptions/20d2016b-1234-1234-1234-be72462e1234/resourceGroups/shrDevopsTesting123/providers/Microsoft.Sql/servers/adv-demo-db-srv-useast"
      location:                              "westeurope"
      name:                                  "adv-demo-db-srv-useast"
      properties.administratorLogin:         "*******"
      properties.administratorLoginPassword: "*******"
      type:                                  "Microsoft.Sql/servers"

Resource changes: 1 to delete, 1 to create.

Are you sure you want to execute the deployment? (y/n): y
{'properties': {'template': <azure.cli.command_modules.resource.custom.JsonCTemplate object at 0x7fb3612dbd68>, 'parameters': {'subscriptionId': {'value': '20d2016b-1234-1234-1234-be72462e1234'}, 'kvName': {'value': 'shrKvTest0830'}, 'tenantPrefix': {'value': 'adv'}, 'environmentSuffix': {'value': 'demo'}, 'regionSuffix': {'value': 'useast'}, 'dbIsEnabled': {'value': True}, 'dbEdition': {'value': 'Premium'}, 'dbAdminUsername': {'value': 'adm'}}, 'mode': 'Complete'}}
{'properties': {'template': <azure.cli.command_modules.resource.custom.JsonCTemplate object at 0x7fb3609fda20>, 'parameters': {'subscriptionId': {'value': '20d2016b-1234-1234-1234-be72462e1234'}, 'kvName': {'value': 'shrKvTest0830'}, 'tenantPrefix': {'value': 'adv'}, 'environmentSuffix': {'value': 'demo'}, 'regionSuffix': {'value': 'useast'}, 'dbIsEnabled': {'value': True}, 'dbEdition': {'value': 'Premium'}, 'dbAdminUsername': {'value': 'adm'}}, 'mode': 'Complete'}}
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
Noneunning ..
id: /subscriptions/20d2016b-1234-1234-1234-be72462e1234/resourceGroups/ShrDevopsTesting123/providers/Microsoft.Resources/deployments/shr
location: null
name: shr
properties:
  correlationId: fd11a316-604a-42cf-a474-c881be120643
  debugSetting: null
  dependencies: []
  duration: PT4M23.8746353S
  error: null
  mode: Complete
  onErrorDeployment: null
  outputResources:
  - id: /subscriptions/20d2016b-1234-1234-1234-be72462e1234/resourceGroups/ShrDevopsTesting123/providers/Microsoft.Sql/servers/adv-demo-db-srv-useast
    resourceGroup: ShrDevopsTesting123
  outputs: null
  parameters:
    dbAdminUsername:
      type: String
      value: adm
    dbEdition:
      type: String
      value: Premium
    dbIsEnabled:
      type: Bool
      value: true
    environmentSuffix:
      type: String
      value: demo
    kvName:
      type: String
      value: shrKvTest0830
    location:
      type: String
      value: westeurope
    regionSuffix:
      type: String
      value: useast
    subscriptionId:
      type: String
      value: 20d2016b-1234-1234-1234-be72462e1234
    tenantPrefix:
      type: String
      value: adv
  parametersLink: null
  providers:
  - id: null
    namespace: Microsoft.Resources
    registrationPolicy: null
    registrationState: null
    resourceTypes:
    - aliases: null
      apiProfiles: null
      apiVersions: null
      capabilities: null
      defaultApiVersion: null
      locationMappings: null
      locations:
      - null
      properties: null
      resourceType: deployments
      zoneMappings: null
  provisioningState: Succeeded
  templateHash: '11903751957628416401'
  templateLink: null
  timestamp: '2021-06-21T06:44:34.352407+00:00'
  validatedResources: null
resourceGroup: ShrDevopsTesting123
tags: null
type: Microsoft.Resources/deployments
Command ran in 333.163 seconds (init: 0.734, invoke: 332.429)

But when I run the exact same command again, it fails (as "predicted" by the output of "-c"):

$ az deployment group create --verbose -c --mode Complete -g "$rgName" -f shr.bicep -p "@$paramFile"
{'properties': {'template': <azure.cli.command_modules.resource.custom.JsonCTemplate object at 0x7fa480cb2a90>, 'parameters': {'subscriptionId': {'value': '20d2016b-1234-1234-1234-be72462e1234'}, 'kvName': {'value': 'shrKvTest0830'}, 'tenantPrefix': {'value': 'adv'}, 'environmentSuffix': {'value': 'demo'}, 'regionSuffix': {'value': 'useast'}, 'dbIsEnabled': {'value': True}, 'dbEdition': {'value': 'Premium'}, 'dbAdminUsername': {'value': 'shradmin'}}, 'mode': 'Complete', 'whatIfSettings': {'resultFormat': 'FullResourcePayloads'}}}
Noneunning ..
KeyVaultParameterReferenceNotFound - The specified KeyVault '/subscriptions/20d2016b-1234-1234-1234-be72462e1234/resourceGroups/ShrDevopsTesting123/providers/Microsoft.KeyVault/vaults/shrKvTest0830' could not be found. Please see https://aka.ms/arm-keyvault for usage details.
Command ran in 34.891 seconds (init: 0.432, invoke: 34.458)

What am I doing wrong? Why is Bicep deleting the Key Vault kv, although I'm referencign it with "existing"? I'm running in mode --mode Complete.

Alexander Skwar
  • 875
  • 1
  • 9
  • 20

2 Answers2

4

Existing keyword means that I know that this resource exists. Its not a part of the deployment template as so - its more like a pointer than actual resource.

When bicep compiles to ARM, resources with existing keyword are transpiled to reference calls. Note, that existing resource might belong to a different scope than you deploy template to.

Because of that, your template does not have a key vault resource as such so in compete mode is scheduled for removal. Complete mode is to ensure that content of the target (usually resource group) will be exactly as the template specifies.

There's no option to exclude a resource from complete mode deployment. You could try put a feature request on bicep's github.

In your case, remove the existing keyword and define how your key vault should exist.

Miq
  • 3,931
  • 2
  • 18
  • 32
  • Thanks a lot for explaining how "existing" works. That was REALLY useful! I think the issue that I'm having especially with the Key Vault is, that I don't yet know how to fill it with random strings, which are acceptable for SQL server passwords. I know how to do that with the az CLI (or PowerShell, I guess). But as there's no "randomString()" function, I'm a bit stuck (uniqueString() isn't good enough for SQL). Somebody suggested to use Azure Deployment Scripts, which I'm going to investigate now. – Alexander Skwar Jun 22 '21 at 06:32
  • Perhaps put the key vault resource in another resource group to avoid deleting it? – Pablo Jomer Jul 01 '21 at 04:37
  • @AlexanderSkwar if you need random passwords in bicep, I'd suggest calling the bicep [`guid(...)`](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-string#guid) or [`newGuid()`](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-string#newguid) function in a loop for each password that is needed. – Al Dass Aug 03 '21 at 18:34
  • This is late but worth mentioning for others... You should be able to define the primary Key Vault resource to keep it from being deleted in Complete without also defining all the values inside it. During my testing, I found that existing key/value pairs are not affected by the deployment mode--values can be added, but existing values are not deleted. In other words, you do not necessarily have to define the random values you want as part of the deployment. – C Perkins May 23 '22 at 14:45
-1

I know I am late to this party, but this is answer to the comment I saw on saving password into KV. If you want random password for your sql, and then save it into kv, instead of using a guid, maybe add the following code in powershell in your pipeline.

This will create a random password for you, and save it into keyvault. I do this before I create the SQL resource for instance, instead of part of keyvault. This means that I update or add for the resource I need it for at the time I need it for, instead of having it as part of KV.


param([string]$secretName,
[string]$kvName)

function Get-RandomPassword {
    param (
        [Parameter(Mandatory)]
        [ValidateRange(4,[int]::MaxValue)]
        [int] $length,
        [int] $upper = 1,
        [int] $lower = 1,
        [int] $numeric = 1,
        [int] $special = 1
    )
    if($upper + $lower + $numeric + $special -gt $length) {
        throw "number of upper/lower/numeric/special char must be lower or equal to length"
    }
    $uCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    $lCharSet = "abcdefghijklmnopqrstuvwxyz"
    $nCharSet = "0123456789"
    $sCharSet = "/*-+,!?=()@;:._"
    $charSet = ""
    if($upper -gt 0) { $charSet += $uCharSet }
    if($lower -gt 0) { $charSet += $lCharSet }
    if($numeric -gt 0) { $charSet += $nCharSet }
    if($special -gt 0) { $charSet += $sCharSet }
    
    $charSet = $charSet.ToCharArray()
    $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
    $bytes = New-Object byte[]($length)
    $rng.GetBytes($bytes)
 
    $result = New-Object char[]($length)
    for ($i = 0 ; $i -lt $length ; $i++) {
        $result[$i] = $charSet[$bytes[$i] % $charSet.Length]
    }
    $password = (-join $result)
    $valid = $true
    if($upper   -gt ($password.ToCharArray() | Where-Object {$_ -cin $uCharSet.ToCharArray() }).Count) { $valid = $false }
    if($lower   -gt ($password.ToCharArray() | Where-Object {$_ -cin $lCharSet.ToCharArray() }).Count) { $valid = $false }
    if($numeric -gt ($password.ToCharArray() | Where-Object {$_ -cin $nCharSet.ToCharArray() }).Count) { $valid = $false }
    if($special -gt ($password.ToCharArray() | Where-Object {$_ -cin $sCharSet.ToCharArray() }).Count) { $valid = $false }
 
    if(!$valid) {
         $password = Get-RandomPassword $length $upper $lower $numeric $special
    }
    return $password
}

try {
        $PassComplexCheck = $false
        do {
            $newPassword=Get-RandomPassword 10 1 1 1 1;

            If ( ($newPassword -cmatch "[A-Z\p{Lu}\s]") `
                -and ($newPassword -cmatch "[a-z\p{Ll}\s]") `
                -and ($newPassword -match "[\d]") `
                -and ($newPassword -match "[^\w]")
                )
                {
                    $PassComplexCheck=$true
                }
        } While ($PassComplexCheck -eq $false)

        az keyvault secret set --vault-name $kvName --name $secretName --value $newPassword -o none
}
catch {
    Write-Host "Secret not generated correctly or saved into keyvault"
    Write-Host $_
}