Search code examples
azureazure-service-fabricazure-resource-managerazure-keyvaultazure-rm-template

How to avoid the status code Conflict when repeatedly calling Import-AzKeyVaultCertificate from an Azure Pipeline?


I am trying to set up a pipeline, which would:

  • Deploy a KeyVault "my-keyvault" by using AzureResourceManagerTemplateDeployment@3 task
  • Run a Powershell script (listed below) to create a self-signed certificate in the keyvault
  • Finally deploy an SF cluster "my-cluster" by using AzureResourceManagerTemplateDeployment@3 task and providing the above certificate both for node-to-node and client-to-node communication (later I plan to introduce 2 different certificates).

My PowerShell script for generating self-signed certificates works well when called for the 1st time:

param(
    [string] [Parameter(Mandatory=$true)] $Password,
    [string] [Parameter(Mandatory=$true)] $CertDnsName,
    [string] [Parameter(Mandatory=$true)] $KeyVaultName,
    [string] [Parameter(Mandatory=$true)] $CertName
)

$SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
$CertFileFullPath = $(Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) "\$CertDnsName.pfx")

$NewCert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -DnsName $CertDnsName 
Export-PfxCertificate -FilePath $CertFileFullPath -Password $SecurePassword -Cert $NewCert

Import-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -FilePath $CertFileFullPath -Password $SecurePassword

However when I call it repeatedly with -

New-ServiceFabricClusterCertificate.ps1 -Password "blah" -CertDnsName "my-hostname" -KeyVaultName "my-keyvault" -CertName "my-cluster-cert"

then I get the error:

Import-AzKeyVaultCertificate : Operation returned an invalid status code 'Conflict'

+ ... NewSecret = Import-AzKeyVaultCertificate -VaultName $KeyVaultName -Na ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (:) [Import-AzKeyVaultCertificate], KeyVaultErrorException
    + FullyQualifiedErrorId : Microsoft.Azure.Commands.KeyVault.ImportAzureKeyVaultCertificate

The Conflict error code is described at the Certificate Creation methods as:

When a KV certificate is created for the first time, an addressable key and secret is also created with the same name as that of the certificate. If the name is already in use, then the operation will fail with an http status code of 409 (conflict). The addressable key and secret get their attributes from the KV certificate attributes. The addressable key and secret created this way are marked as managed keys and secrets, whose lifetime is managed by Key Vault. Managed keys and secrets are read-only. Note: If a KV certificate expires or is disabled, the corresponding key and secret will become inoperable.

If this is the first operation to create a KV certificate then a policy is required. A policy can also be supplied with successive create operations to replace the policy resource. If a policy is not supplied, then the policy resource on the service is used to create a next version of KV certificate. Note that while a request to create a next version is in progress, the current KV certificate, and corresponding addressable key and secret, remain unchanged.

My problem is that I don't understand what it means and what to do to enable repeated calls of my PowerShell scripts.

I have tried deleting the certificate manually form the KV and also searched for Keys and Secrets in the same KV with the same name (since the doc tells "an addressable key and secret is also created") - but that does not help.

Now I get the Conflict error whenever I run the script!


Solution

  • The following pipeline tasks have solved the problem for me -

    • First I delete the self-signed certificate from KeyVault (it is ok to fail)
    • Then I purge the self-signed certificate from KeyVault (it is ok to fail)
    • Finally I import the self-signed certificate again

    And note that the purge command uses the deletedcertificates id.

    # Note: to generate a self-signed cert, follow the following steps:
    # $SecurePassword = ConvertTo-SecureString -String 'password123' -AsPlainText -Force
    # $NewCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName 'myhost.westeurope.cloudapp.azure.com'
    # Export-PfxCertificate -FilePath C:\Temp\my-self-signed-cert.pfx -Password $SecurePassword -Cert $NewCert
    # $Bytes = [System.IO.File]::ReadAllBytes('C:\temp\my-self-signed-cert.pfx')
    # $Base64 = [System.Convert]::ToBase64String($Bytes)
    # After that please copy-paste the Base64 content into the task below
    
    # purge the self-signed cert from the Keyvault to avoid conflict; ignore failures
    - task: AzureCLI@2
      inputs:
        azureSubscription: '${{ parameters.mysub }}'
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        powerShellErrorActionPreference: 'silentlyContinue'
        inlineScript: |
          az keyvault certificate delete --vault-name mykeyvault --id 'https://mykeyvault.vault.azure.net/certificates/my-self-signed-cert'
          az keyvault certificate purge --vault-name mykeyvault --id 'https://mykeyvault.vault.azure.net/deletedcertificates/my-self-signed-cert'
    
    # import the self-signed certificate into the Keyvault
    - task: AzurePowerShell@5
      inputs:
        azureSubscription: '${{ parameters.mysub }}'
        ScriptType: 'InlineScript'
        azurePowerShellVersion: '3.1.0'
        Inline: |
          $Pwd = ConvertTo-SecureString -String 'password123' -Force -AsPlainText
          $Base64 = 'MIIK....3000_chars_here_base64_encoded_...U1ICAgfQ=='
          $Cert = Import-AzKeyVaultCertificate -VaultName mykeyvault -Name my-self-signed-cert -CertificateString $Base64 -Password $Pwd
          echo "##vso[task.setvariable variable=Thumbprint;isOutput=true]$Cert.Thumbprint"