3 ways to assign access policy for user-assigned managed identity on key vault using ARM template

This post is a summary of my experience dealing with user-assigned managed identity and key vaults in Azure, it explores multiple ways to achieve the same result – how to assign access policies using an ARM template. Each of the ways has its own pros and cons.

First, the simplest: to create a key vault with preassigned access policy:

{
  "resources": [
    {
      "type": "Microsoft.KeyVault/vaults",
      "apiVersion": "[variables('kvApiVersion')]",
      "name": "[parameters('kvName')]",
      "location": "[parameters('location')]",
      "properties": {
        "tenantId": "[variables('tenantId')]",
        "accessPolicies": "[parameters('accessPolicies')]",
        "sku": {
          "name": "Standard",
          "family": "A"
        }
      }
    }
  ]
}

The pros of this approach are same as the cons: you have to know all access policies ahead of time. That works but only in the simplest scenarios, such as for security groups as they’re created outside of ARM and have static, well-known OID.

Second: to create a key vault, then a user-assigned managed identity, and then add an access policy:

{
  "variables": {
    "uaidRef": "[concat('Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('uaidName'))]",
  },
  "resources": [
    {
      "type": "Microsoft.KeyVault/vaults",
      "apiVersion": "[variables('kvApiVersion')]",
      "name": "[parameters('kvName')]",
      "location": "[parameters('location')]",
      "properties": {
        "tenantId": "[variables('tenantId')]",
        "accessPolicies": [],
        "sku": {
          "name": "Standard",
          "family": "A"
        }
      }
    },
    {
      "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
      "apiVersion": "[variables('idApiVersion')]",
      "name": "[parameters('uaidName')]",
      "location": "[parameters('location')]"
    },
    {
      "type": "Microsoft.KeyVault/vaults/accessPolicies",
      "name": "[concat(parameters('kvName'), '/add')]",
      "apiVersion": "[variables('kvApiVersion')]",
      "properties": {
        "accessPolicies": [
          {
            "tenantId": "[variables('tenantId')]",
            "objectId": "[reference(variables('uaidRef'), variables('idApiVersion')).principalId]",
            "permissions": "[variables('uaidPermissions')]"
          }
        ]
      },
      "dependsOn": [
        "[concat('Microsoft.KeyVault/vaults/', parameters('kvName'))]",
        "[concat('Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('uaidName'))]",
      ]
    }
  ]
}

The main drawback of this one is in the effect of eviction. Since a deployment of ARM template is effectively a PUT on the respective resource, immediately after the creation, a key vault has no access policies. What means all requests to access it will fail 403 until the respective polices are not added back. The time window might be relatively short but still exist what’s may and will cause outages and incidents.

Moreover Key Vault doesn’t support adding access policies in parallel. What means that if there are multiple policies to add they must be added sequentially. Each takes several seconds what increases the window up to a minute or more. If this is a production environment then this is guaranteed to have customer impact, makes it impossible to deployment transparently and without interruption of running services, violates one of the core principles of cloud and enterprise grade infrastructure.

Finally, third: create a user-assigned managed identity, then create a key vault with preassigned access policy:

{
  "variables": {
    "uaidRef": "[concat('Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('uaidName'))]"
  },
  "resources": [
    {
      "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
      "apiVersion": "[variables('idApiVersion')]",
      "name": "[parameters('uaidName')]",
      "location": "[parameters('location')]"
    },
    {
      "type": "Microsoft.KeyVault/vaults",
      "apiVersion": "[variables('kvApiVersion')]",
      "name": "[parameters('kvName')]",
      "location": "[parameters('location')]",
      "properties": {
        "tenantId": "[variables('tenantId')]",
        "accessPolicies": [
          {
            "tenantId": "[variables('tenantId')]",
            "objectId": "[reference(variables('uaidRef'), variables('idApiVersion')).principalId]",
            "permissions": "[variables('uaidPermissions')]"
          }
        ],
        "sku": {
          "name": "Standard",
          "family": "A"
        }
      },
      "dependsOn": [
        "[concat('Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('uaidName'))]"
      ]
    }
  ]
}

This one basically combined the pros of the latter two and in my mind has no cons. It eliminates the window altogether, the key vault would never have no access polices even again.

This entry was posted in Infrastructure and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.