Search code examples
azureazure-pipelinesazure-service-fabricazure-resource-managerazure-keyvault

How to use Azure Key Vault to pass secure parameter value during deployment?


I have an ARM template for deploying a Service Fabric cluster in Azure pipeline:

- task: AzureResourceManagerTemplateDeployment@3
  displayName: 'Deploy SF cluster'
  inputs:
    deploymentScope: 'Resource Group'
    subscriptionId: '${{ parameters.SubscriptionId }}'
    azureResourceManagerConnection: '${{ parameters.ArmConnection }}'
    action: 'Create Or Update Resource Group'
    resourceGroupName: '${{ parameters.ResourceGroupName }}'
    location: 'West Europe'
    templateLocation: 'Linked artifact'
    csmFile: '$(Build.SourcesDirectory)/pipelines/templates/sfcluster.json'
    csmParametersFile: '$(Build.SourcesDirectory)/pipelines/templates/sfcluster-params.json'
    overrideParameters: '-certificateThumbprint $(Thumbprint) -sourceVaultResourceId $(KeyvaultId) -certificateUrlValue $(SecretId)'
    deploymentMode: 'Incremental'

The ARM template works well (since many days and in 5 different pipelines) when I use:

"osProfile": {
    "adminUsername": "RdpUsername",
    "adminPassword": "RdpPassword",

    "computernamePrefix": "[variables('vmNodeType0Name')]",
    "secrets": [
        {
            "sourceVault": {
                "id": "[parameters('sourceVaultResourceId')]"
            },
            "vaultCertificates": [
                {
                    "certificateStore": "My",
                    "certificateUrl": "[parameters('certificateUrlValue')]"
                }
            ]
        }
    ]
},

Having hardcoded RDP credentials is however a security issue, so I would like to Use Azure Key Vault to pass secure parameter value during deployment

So I add 2 random secret strings to the Keyvault, which is deployed earlier in the pipeline and then try:

"osProfile": {
    "adminUsername": {
        "reference": {
            "keyVault": {
                "id": "[parameters('sourceVaultResourceId')]"
            },
            "secretName": "RdpUsername"
        }
    },
    "adminPassword": {
        "reference": {
            "keyVault": {
                "id": "[parameters('sourceVaultResourceId')]"
            },
            "secretName": "RdpPassword"
        }
    },

    "computernamePrefix": "[variables('vmNodeType0Name')]",
    "secrets": [
        {
            "sourceVault": {
                "id": "[parameters('sourceVaultResourceId')]"
            },
            "vaultCertificates": [
                {
                    "certificateStore": "My",
                    "certificateUrl": "[parameters('certificateUrlValue')]"
                }
            ]
        }
    ]
},

Unfortunately this produces the error:

screenshot pipeline

Unexpected character encountered while parsing value: {. Path 'properties.virtualMachineProfile.osProfile.adminUsername', line 1, position 3178.

My question is: why is the bracket { unexpected there?

My ARM template is based on the azure-quickstart-templates/service-fabric-secure-cluster-5-node-1-nodetype and as written above works with the hardcoded values.

UPDATE:

Here is my sfcluster-params.json (but also please note the overrideParameters in the pipeline task listed below and being filled with pipeline variables set by Keyvault deployment):

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "clusterName": {
            "value": "sfcluster"
        }
    }
}

And here a bigger part of my Azure pipeline:

  # deploy KeyVault by ARM template and output VinHashKey and SF Cluster certificate thumbsprint
  - task: AzureResourceManagerTemplateDeployment@3
    displayName: 'Deploy Keyvault'
    inputs:
      deploymentScope: 'Resource Group'
      subscriptionId: '${{ parameters.SubscriptionId }}'
      azureResourceManagerConnection: '${{ parameters.ArmConnection }}'
      action: 'Create Or Update Resource Group'
      resourceGroupName: '${{ parameters.ResourceGroupName }}'
      location: 'West Europe'
      templateLocation: 'Linked artifact'
      csmFile: '$(Build.SourcesDirectory)/pipelines/templates/keyvault.json'
      csmParametersFile: '$(Build.SourcesDirectory)/pipelines/templates/keyvault-params.json'
      deploymentMode: 'Incremental'

  # collect outputs from the above ARM task and put them into pipeline vars
  - task: ARM Outputs@5
    displayName: 'Collect Keyvault output'
    inputs:
      ConnectedServiceNameSelector: 'ConnectedServiceNameARM'
      ConnectedServiceNameARM: '${{ parameters.ArmConnection }}'
      resourceGroupName: '${{ parameters.ResourceGroupName }}'
      whenLastDeploymentIsFailed: 'fail'

  # import the certificate my-self-signed-cert into the Keyvault if it is missing there
  - task: AzurePowerShell@5
    displayName: 'Import certificate'
    inputs:
      azureSubscription: '${{ parameters.ArmConnection }}'
      ScriptType: 'InlineScript'
      azurePowerShellVersion: '3.1.0'
      Inline: |
        $Cert = Get-AzKeyVaultCertificate -VaultName $(KeyVaultName) -Name my-self-signed-cert
        if (!$Cert) {
            $Pwd = ConvertTo-SecureString -String MyPassword -Force -AsPlainText
            $Base64 = 'MII....gfQ'
            $Cert = Import-AzKeyVaultCertificate -VaultName $(KeyVaultName) -Name my-self-signed-cert -CertificateString $Base64 -Password $Pwd
        }
        # set the pipeline variables Thumbprint and SecretId - needed for SF deployment
        echo "##vso[task.setvariable variable=Thumbprint]$($Cert.Thumbprint)"
        echo "##vso[task.setvariable variable=SecretId]$($Cert.SecretId)"

  # deploy SF cluster by ARM template and use the SF certificate thumbsprint as admin cert
  - task: AzureResourceManagerTemplateDeployment@3
    displayName: 'Deploy SF cluster'
    inputs:
      deploymentScope: 'Resource Group'
      subscriptionId: '${{ parameters.SubscriptionId }}'
      azureResourceManagerConnection: '${{ parameters.ArmConnection }}'
      action: 'Create Or Update Resource Group'
      resourceGroupName: '${{ parameters.ResourceGroupName }}'
      location: 'West Europe'
      templateLocation: 'Linked artifact'
      csmFile: '$(Build.SourcesDirectory)/pipelines/templates/sfcluster.json'
      csmParametersFile: '$(Build.SourcesDirectory)/pipelines/templates/sfcluster-params.json'
      overrideParameters: '-certificateThumbprint $(Thumbprint) -sourceVaultResourceId $(KeyvaultId) -certificateUrlValue $(SecretId)'
      deploymentMode: 'Incremental'

Finally, here the sfcluster.json - it is too big to put at Stackoverflow.


Solution

  • A keyVault reference can only be used as a parameter value - so in a param file or the parameters property of a deployment. You can't use it directly as a property value on a resource.