Poul K. Sørensen
Poul K. Sørensen

Reputation: 17560

How to update a Secret in Azure Key Vault only if changed in ARM templates or check if it exists

I have a production keyvault that keeps a reference of secrets that projects can use, but only if deployed using ARM templates such secrets are not handled by people copy pasting them.

When a new project starts, as part of its deployment script, it will create its own keyvault.

I want to be able to run the templates/scripts as part of CI/CD. And this will today result in the same secret having a new version at each run, even though the value did not change.

How to make it only update the keyvault value when the master vault is updated.

Upvotes: 2

Views: 3292

Answers (1)

Poul K. Sørensen
Poul K. Sørensen

Reputation: 17560

In my deployment.sh script I use the following technique.

SendGridUriWithVersion=$((az group deployment create ... assume that the secret exists ... || az group deployment create ... assume that the secret exists ... ) | jq -r '.properties.outputs.secretUriWithVersion.value')

and it works because in the template there is a parameter, if set, that will retrieve the secret and compare it with the new value and only insert if difference. The original problem is that the deployment fails if the secret is not already set (this happens for the first deployment etc).

But then due to Unix ||, the same script is run again without the parameter set and it will use a condition to not try to get the old value and therefore run successful.

Here are the example in dept:

      SecretName="Sendgrid"
      SourceSecretName="Sendgrid"
      SourceVaultName="io-board"
      SourceResourceGroup="io-board" 
      SendGridUriWithVersion=$((az group deployment create -n ${SecretName}-secret -g $(prefix)-$(projectName)-$(projectEnv) --template-uri https://management.dotnetdevops.org/providers/DotNetDevOps.AzureTemplates/templates/KeyVaults/${keyVaultName}/secrets/${SecretName}?sourced=true --parameters sourceVault=${SourceVaultName} sourceResourceGroup=${SourceResourceGroup} sourceSecretName=${SourceSecretName} update=true || az group deployment create -n ${SecretName}-secret -g $(prefix)-$(projectName)-$(projectEnv) --template-uri https://management.dotnetdevops.org/providers/DotNetDevOps.AzureTemplates/templates/KeyVaults/${keyVaultName}/secrets/${SecretName}?sourced=true --parameters sourceVault=${SourceVaultName} sourceResourceGroup=${SourceResourceGroup} sourceSecretName=${SourceSecretName}) | jq -r '.properties.outputs.secretUriWithVersion.value')

The https://management.dotnetdevops.org/providers/DotNetDevOps.AzureTemplates/templates/KeyVaults/{keyvaultName}/secrets/{secretName}?sourced=true returns a template

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "keyVaultName": {
      "type": "string",
      "defaultValue": "io-board-data-ingest-dev"
    },
    "secretName": {
      "type": "string",
      "metadata": {
        "description": "Name of the secret to store in the vault"
      },
      "defaultValue": "DataStorage"
    },
    "sourceVaultSubscription": {
      "type": "string",
      "defaultValue": "[subscription().subscriptionId]"
    },
    "sourceVault": {
      "type": "string",
      "defaultValue": "[subscription().subscriptionId]"
    },
    "sourceResourceGroup": {
      "type": "string",
      "defaultValue": "[resourceGroup().name]"
    },
    "sourceSecretName": {
      "type": "string"
    },
    "update": {
      "type": "bool",
      "defaultValue": false
    }
  },
  "variables": {
    "empty": {
      "value": ""
    },
    "test": {
      "reference": {
        "keyVault": {
          "id": "[resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
        },
        "secretName": "[parameters('secretName')]"
      }
    }
  },
  "resources": [
    {
      "apiVersion": "2018-05-01",
      "name": "AddLinkedSecret",
      "type": "Microsoft.Resources/deployments",
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "[concat('https://management.dotnetdevops.org/providers/DotNetDevOps.AzureTemplates/templates/KeyVaults/',parameters('keyVaultName'),'/secrets/',parameters('secretName'))]",
          "contentVersion": "1.0.0.0"
        },
        "parameters": {
          "existingValue": "[if(parameters('update'),variables('test'),variables('empty'))]",
          "secretValue": {
            "reference": {
              "keyVault": {
                "id": "[resourceId(parameters('sourceVaultSubscription'), parameters('sourceResourceGroup'), 'Microsoft.KeyVault/vaults', parameters('sourceVault'))]"
              },
              "secretName": "[parameters('sourceSecretName')]"
            }
          }
        }
      }
    }
  ],
  "outputs": {
    "secretUriWithVersion": {
      "type": "string",
      "value": "[reference('AddLinkedSecret').outputs.secretUriWithVersion.value]"
    }
  }
}

and that template has a nested call to https://management.dotnetdevops.org/providers/DotNetDevOps.AzureTemplates/templates/KeyVaults/{keyvaultName}/secrets/{secretName} which gives the one with the condition

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "keyVaultName": {
      "type": "string",
      "defaultValue": "io-board-data-ingest-dev",
      "metadata": {
        "description": "Name of the existing vault"
      }
    },
    "secretName": {
      "type": "string",
      "metadata": {
        "description": "Name of the secret to store in the vault"
      },
      "defaultValue": "DataStorage"
    },
    "secretValue": {
      "type": "securestring",
      "metadata": {
        "description": "Value of the secret to store in the vault"
      }
    },
    "existingValue": {
      "type": "securestring",
      "defaultValue": ""
    }
  },
  "variables": {},
  "resources": [
    {
      "type": "Microsoft.KeyVault/vaults/secrets",
      "condition": "[not(equals(parameters('existingValue'),parameters('secretValue')))]",
      "apiVersion": "2015-06-01",
      "name": "[concat(parameters('keyVaultName'), '/', parameters('secretName'))]",
      "properties": {
        "value": "[parameters('secretValue')]"
      }
    }
  ],
  "outputs": {
    "secretUriWithVersion": {
      "type": "string",
      "value": "[reference(resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretName')), '2015-06-01').secretUriWithVersion]"
    }
  }
}

Upvotes: 2

Related Questions