Search code examples
azurekeyazure-keyvaultazure-application-gateway

Azure Key Vault access policies and Managed Identities (ARM templates)


Looking for expertise to help.

I have created ARM template, which deploys Azure Application Gateway and Key Vault instances. I want to give principalID (user assigned managed identity) of App Gateway in Key Vault to get certificate or secret but it fails with an error:

"Deployment template validation failed: 'The template resource 'kv-project-dev/add' at line '1' and column '21178' is not valid: The language expression property 'principalId' can't be evaluated.. Please see https://aka.ms/arm-template-expressions for usage details."

It works fine if I add principalID value of user managed identity in the parameters.json file manually (as objectID), however, I am looking how to automate this part and add principalID during ARM template deployment automatically in Key Vault access policies.

It seems an issue with this line in parameters.json file:

"objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('appgw-managed-id')).principalId)]"

The following resources used to create ARM template: New way to reference managed identity in ARM template and App Gateway v2 deployment with Key Vault

Parameters.json file looks for access policies:

"kv-servicePrincipalObjects": {
  "value": [
    {
    "tenantId": "[subscription().tenantid]",
    "objectId": "xxxx-xxxx-xxxx-xxxx-xxxx",
    "permissions": {
        "keys": [],
        "secrets": ["get","list"],
        "certificates": []
    },
    "applicationId": ""
    },
    {
      "tenantId": "[subscription().tenantid]",
      "objectId": "yyyy-yyyy-yyyy-yyyy-yyyy",
      "permissions": {
          "keys": ["all"],
          "secrets": ["all"],
          "certificates": ["all"]
      },
      "applicationId": ""
    },
    {
      "tenantId": "[subscription().tenantid]",
      "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('appgw-managed-id')).principalId)]",
      "permissions": {
          "keys": [],
          "secrets": ["get"],
          "certificates": ["get"]
      },
      "applicationId": ""
    }
  ]
},

Template.json file to create Key Vault and assign access policies:

{
        "type": "Microsoft.KeyVault/vaults",
        "apiVersion": "2019-09-01",
        "name": "[variables('kv-name')]",
        "location": "[parameters('location')]",
        "tags": "[parameters('resource-Tags')]",
        "dependsOn": [
            "[resourceId('Microsoft.Network/virtualNetworks', variables('vnet-name'))]",
            "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('appgw-managed-id'))]"
        ],
         "properties": {
            "enabledForDeployment": "[parameters('kv-enabledForDeployment')]",
            "enabledForTemplateDeployment": "[parameters('kv-enabledForTemplateDeployment')]",
            "enabledForDiskEncryption": "[parameters('kv-enabledForDiskEncryption')]",
            "enableRbacAuthorization": "[parameters('kv-enableRbacAuthorization')]",
            "accessPolicies": [],
            "tenantId": "[parameters('tenant')]",
            "sku": {
                "name": "[parameters('kv-sku')]",
                "family": "A"
            },
            "enableSoftDelete": "[parameters('kv-enableSoftDelete')]",
            "softDeleteRetentionInDays": "[parameters('kv-softDeleteRetentionInDays')]",
            "enablePurgeProtection": "[parameters('kv-enablePurgeProtection')]",
            "KVvirtualNetworkConfiguration": {
                "KVsubnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnet-name'), 'kv-subnet')]"
            },
            "networkAcls": {
                "defaultAction": "Deny",
                "bypass": "AzureServices",
                "virtualNetworkRules": [
                {
                    "id": "[concat(parameters('kv-virtualNetworks'), '/subnets/','kv-subnet')]",
                    "ignoreMissingVnetServiceEndpoint": false
                }
                ],
                "ipRules": "[parameters('kv-ipRules')]"
            }
        }
    },
{
        "type": "Microsoft.KeyVault/vaults/accessPolicies",
        "name": "[concat(variables('kv-name'), '/add')]",
        "apiVersion": "2019-09-01",
            "dependsOn": [
                "[resourceId('Microsoft.KeyVault/vaults', variables('kv-name'))]"
            ],
        "properties": {
            "copy": [
            {
                "name": "accessPolicies",
                "count": "[length(parameters('kv-servicePrincipalObjects'))]",
                "input": {
                    "tenantId": "[parameters('kv-servicePrincipalObjects')[copyIndex('accessPolicies')].tenantId]",
                    "objectId": "[parameters('kv-servicePrincipalObjects')[copyIndex('accessPolicies')].objectId]",
                    "permissions": {
                        "keys": "[parameters('kv-servicePrincipalObjects')[copyIndex('accessPolicies')].permissions.keys]",
                        "secrets": "[parameters('kv-servicePrincipalObjects')[copyIndex('accessPolicies')].permissions.secrets]",
                        "certificates": "[parameters('kv-servicePrincipalObjects')[copyIndex('accessPolicies')].permissions.certificates]"
                    },
                    "applicationId": "[parameters('kv-servicePrincipalObjects')[copyIndex('accessPolicies')].applicationId]"
                }
            }
            ]
        }
    }

Solution

  • This line had a typo in it:

    "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('appgw-managed-id')).principalId)]"
    

    It should be:

    "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('appgw-managed-id'))).principalId]"
    

    Note we moved the closing parenthesis at the end. The line was trying to get the principalId property from the result of resourceId(), when it needs to get it from the object returned by reference().