5

I think Managed Service Identity is a great concept and I love keyvault. However:

When I use the script using an incremental resource group deployment:

Sample is modified for brevity

{
      "type": "Microsoft.KeyVault/vaults",
      "name": "[parameters('keyvaultName')]",
      "apiVersion": "2015-06-01",
      "properties": {            
        "accessPolicies": [
          {
            "objectId": "[reference(parameters('functionAppName'), '2016-08-01', 'Full').identity.principalId]",
            "permissions": {
              "keys": [],
              "secrets": [
                "Get"
              ]
            }
          }
        ]
      },
      "dependsOn": [
        "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]"
      ]
    },
    {
      "apiVersion": "2016-08-01",
      "type": "Microsoft.Web/sites",
      "name": "[parameters('functionAppName')]",
      "kind": "functionapp",
      "identity": {
        "type": "SystemAssigned"
      },
    }

It deploys successfully and adds the MSI to keyvault, but --

It blows away the already assigned access policies. Is it possible for arm to preserve accessPolicies and only add/update policies that match?

Without this it's impossible to fully script a deployment with a MSI and also assign the principal to keyvault..

Am I missing something?

Hoffmania
  • 926
  • 2
  • 7
  • 15
  • 1
    Can you confirm you are running in incremental mode? – CtrlDot Dec 06 '17 at 05:13
  • Yep it was running incremental mode. – Hoffmania Dec 06 '17 at 05:29
  • 1
    Do you check this [example](https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet)? – Shui shengbao Dec 06 '17 at 05:37
  • `"identityResourceId": "[concat(resourceId('Microsoft.Web/sites', parameters('webSiteName')),'/providers/Microsoft.ManagedIdentity/Identities/default')]"` object id should be `"objectId": "[reference(variables('identityResourceId'), '2015-08-31-PREVIEW').principalId]",` – Shui shengbao Dec 06 '17 at 05:38
  • adding the principal using the method above works. I had already added myself as a user to accessPolicies and using the script I was removed. which is not what I expected. – Hoffmania Dec 06 '17 at 05:45
  • that is a sweet example btw :) @ShengbaoShui-MSFT – Hoffmania Dec 06 '17 at 05:46
  • oh- and I removed some of the required properties like tenantId from my sample for bevity.. – Hoffmania Dec 06 '17 at 05:48
  • @Hoffmania If my understanding is right, you could add or update permissions in your template and re-deploy it and it should work. – Shui shengbao Dec 06 '17 at 06:01
  • @Hoffmania Do you see this template https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/blob/master/azuredeploy.json – Shui shengbao Dec 06 '17 at 06:02
  • 1
    @ShengbaoShui-MSFT that's true.. I could add all permissions to the template and it will work.. but that means I need a script that contains all MSI and principals that access a particular vault.. that's not tractable – Hoffmania Dec 06 '17 at 06:12
  • Did anyone of you find a solution for this? – Wessel Kranenborg Jun 07 '18 at 09:58

2 Answers2

5

As the author of the blog post, I'll post the details per the mods:

When you deploy a resource of type Microsoft.KeyVault/vaults/accessPolicies with the name “add”, it will merge in your changes. This special child resource type was created to allow Managed Service Identity scenarios where you don’t know the identity of a VM until the VM is deployed and you want to give that identity access to the vault during deployment.

An incremental deployment can be used along with this json to achieve the objective:

{
    "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vaultName": {
            "type": "string"
        }
    },
    "resources": [
        {
            "type": "Microsoft.KeyVault/vaults/accessPolicies",
            "name": "[concat(parameters('vaultName'), '/add')]",
            "apiVersion": "2016-10-01",
            "properties": {
                "accessPolicies": [
                    {
                        "tenantId": "dfe47ca8-acfc-4539-9519-7d195a9e79e4",
                        "objectId": "5abe9358-10ae-4195-ba23-d34111430329",
                        "permissions": {
                            "keys": ["all"],
                            "secrets": ["all"],
                            "certificates": ["all"],
                            "storage": ["all"]
                        }
                    }
                ]
            }
        }
    ],
    "outputs": {
    }
}
Matt Small
  • 2,182
  • 1
  • 10
  • 16
  • 1
    What should be the `vaultName` there ? is it just a string or url ? Also, if that keyvault exists in another resource group then should we give resource id (long uri) of keyvault ? – Venkata Dorisala Aug 03 '18 at 13:18
  • 1
    This doesn't really solve the problem all the way. If you create the vault via template, it will remove all existing policies. So, if you want incremental policies, you have to isolate them into an entirely separate template. Since most resources work incrementally, this really just leads to spinning the vault off into a standalone template. – friggle Oct 31 '19 at 20:33
  • This doesn't solve the initial problem. If I deploy the vault's ARM template, it will clear my policies which is completely contrary to the way other incremental deployments work. – mdarefull Nov 08 '21 at 21:41
2

The issue with the highest-voted answer is that it removes the key vault from the ARM template altogether, meaning that the key vault's creation becomes a manual process on new environments.

ARM does not allow a key vault to be redeployed without clearing its existing access policies. The accessPolicies property is required (except when recovering a deleted vault), so omitting it will cause an error. Setting it to [] will clear all existing policies. There has been a Microsoft Feedback request to fix this since 2018, currently with 152 votes.

The best way I've found of working around this is to make the key vault deployed conditionally only if it does not already exist, and define the access policies through a separate add child resource. This causes the specified policies to get added or updated, whilst preserving any other existing policies. I check whether the key vault already exists by passing in the list of existing resource names to the ARM template.

In the Azure pipeline:

- task: AzurePowerShell@5
  displayName: 'Get existing resource names'
  inputs:
    azureSubscription: '$(armServiceConnection)'
    azurePowerShellVersion: 'LatestVersion'
    ScriptType: 'InlineScript'
    Inline: |      
      $resourceNames = (Get-AzResource -ResourceGroupName $(resourceGroupName)).Name | ConvertTo-Json -Compress
      Write-Output "##vso[task.setvariable variable=existingResourceNames]$resourceNames"
    azurePowerShellVersion: 'LatestVersion'

- task: AzureResourceManagerTemplateDeployment@3
  name: DeployResourcesTemplate
  displayName: 'Deploy resources through ARM template
  inputs:
    deploymentScope: 'Resource Group'
    action: 'Create Or Update Resource Group'
    # ...
    overrideParameters: >-
      -existingResourceNames $(existingResourceNames)
      # ...
    deploymentMode: 'Incremental'

In the ARM template:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",

  "parameters": {
    "keyVaultName": {
      "type": "string"
    },
    "existingResourceNames": {
      "type": "array",
      "defaultValue": []
    }
  },

  "resources": [
    {
      "type": "Microsoft.KeyVault/vaults",
      "apiVersion": "2016-10-01",
      "name": "[parameters('keyVaultName')]",
      "location": "[resourceGroup().location]",
      // Only deploy the key vault if it does not already exist.
      // Conditional deployment doesn't cascade to child resources, which can be deployed even when their parent isn't.
      "condition": "[not(contains(parameters('existingResourceNames'), parameters('keyVaultName')))]",
      "properties": {
        "sku": {
          "family": "A",
          "name": "Standard"
        },
        "tenantId": "[subscription().tenantId]",
        "enabledForDeployment": false,
        "enabledForDiskEncryption": false,
        "enabledForTemplateDeployment": true,
        "enableSoftDelete": true,
        "accessPolicies": []
      },
      "resources": [
        {
          "type": "accessPolicies",
          "apiVersion": "2016-10-01",
          "name": "add",
          "location": "[resourceGroup().location]",
          "dependsOn": [
            "[parameters('keyVaultName')]"
          ],
          "properties": {
            "accessPolicies": [
              // Specify your access policies here.
              // List does not need to be exhaustive; other existing access policies are preserved.
            ]
          }
        }
      ]
    }
  ]
}
Douglas
  • 53,759
  • 13
  • 140
  • 188