2

I'm trying to incorporate some additional security features on my resources such as encryption using customer managed keys. For the Service Bus, this requires the Service Bus to have a managed identity created and granted access to the Key Vault. However, the managed identity is not known until after the Service Bus exists.

In my ARM template, I need to initialize the Service Bus without encryption in order to get a managed identity, grant that identity access to key vault, then update the Service Bus with encryption. However, this deployment process is not repeatable. On subsequent re-deployments, it will fail because encryption cannot be removed from a Service Bus once it is granted. Even if it did work, I would be performing unnecessary steps to remove encryption and add it back on every deployment. It seems that an IAC template that describes the expected final state of the resource cannot be used to both maintain and bootstrap a new environment.

The same problem exists for API Management, I want to add custom domains but they require Key Vault access. It means I can't re-deploy my ARM template without removing the custom domains when the init step gets repeated (or keep a separate set of templates for 'initialization' vs the 'real deployment'.

Is there a better solution for this? I looked into user assigned identities which seems like it could solve the problem, but they aren't supported in ARM templates. I checked if there is a way to make the 'init' step conditional by checking if the resource already exists, and this is also not supported via ARM template.

Community
  • 1
  • 1
siegelc
  • 33
  • 5

1 Answers1

2

If you want to maintain a single ARM template, you can use nested deployments to define a resource then reference it again to update it.

In the following example, I create the Service Bus with a system-assigned managed identity, a Key Vault, and an RSA key. In a nested deployment within the same ARM template that depends on that key being generated, I then update the Service Bus to enable encryption. An advantage of being all in the same template is that resourceId and reference can be used with abbreviated syntax (i.e. just the resource name).

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "name": {
            "type": "string",
            "defaultValue": "[resourceGroup().name]"
        },
        "location": {
            "type": "string",
            "defaultValue": "[resourceGroup().location]"
        },
        "tenantId": {
            "type": "string",
            "defaultValue": "[subscription().tenantId]"
        }
    },
    "variables": {
        "kv_name": "[concat(parameters('name'), 'kv')]",
        "kv_version": "2019-09-01",
        "sb_name": "[concat(parameters('name'), 'sb')]",
        "sb_version": "2018-01-01-preview",
        "sb_keyname": "sbkey"
    },
    "resources": [
        {
            "type": "Microsoft.ServiceBus/namespaces",
            "apiVersion": "[variables('sb_version')]",
            "name": "[variables('sb_name')]",
            "location": "[parameters('location')]",
            "sku": {
                "name": "Premium",
                "tier": "Premium",
                "capacity": 1
            },
            "identity": {
                "type": "SystemAssigned"
            },
            "properties": {
                "zoneRedundant": false
            }
        },
        {
            "type": "Microsoft.KeyVault/vaults",
            "apiVersion": "[variables('kv_version')]",
            "name": "[variables('kv_name')]",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[variables('sb_name')]"
            ],
            "properties": {
                "sku": {
                    "family": "A",
                    "name": "Standard"
                },
                "tenantId": "[parameters('tenantId')]",
                "accessPolicies": [
                    {
                        "tenantId": "[reference(variables('sb_name'), variables('sb_version'), 'Full').identity.tenantId]",
                        "objectId": "[reference(variables('sb_name'), variables('sb_version'), 'Full').identity.principalId]",
                        "permissions": {
                            "keys": [
                                "get",
                                "wrapKey",
                                "unwrapKey"
                            ]
                        }
                    }
                ],
                // Both must be enabled to encrypt Service Bus at rest.
                "enableSoftDelete": true,
                "enablePurgeProtection": true
            }
        },
        {
            "type": "Microsoft.KeyVault/vaults/keys",
            "apiVersion": "[variables('kv_version')]",
            "name": "[concat(variables('kv_name'), '/', variables('sb_keyname'))]",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[variables('kv_name')]"
            ],
            "properties": {
                "kty": "RSA",
                "keySize": 2048,
                "keyOps": [
                    "wrapKey",
                    "unwrapKey"
                ],
                "attributes": {
                    "enabled": true
                }
            }
        },
        {
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2020-10-01",
            "name": "sb_deployment",
            "dependsOn": [
                "[resourceId('Microsoft.KeyVault/vaults/keys', variables('kv_name'), variables('sb_keyname'))]"
            ],
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "resources": [
                        {
                            "type": "Microsoft.ServiceBus/namespaces",
                            "apiVersion": "[variables('sb_version')]",
                            "name": "[variables('sb_name')]",
                            "location": "[parameters('location')]",
                            "sku": {
                                "name": "Premium",
                                "tier": "Premium",
                                "capacity": 1
                            },
                            "identity": {
                                "type": "SystemAssigned"
                            },
                            "properties": {
                                "zoneRedundant": false,
                                "encryption": {
                                    "keySource": "Microsoft.KeyVault",
                                    "keyVaultProperties": [
                                        {
                                            // Ideally should specify a specific version, but no ARM template function to get this currently.
                                            "keyVaultUri": "[reference(variables('kv_name')).vaultUri]",
                                            "keyName": "[variables('sb_keyname')]"
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            }
        }
    ]
}

To deploy this using the Azure CLI:

az group create -g rg-example -l westus2
az deployment group create -g rg-example -f template.json --parameters name=example
Heath
  • 2,986
  • 18
  • 21
  • Whether the template is nested or not, splitting the service bus deployment into two steps introduces the issue I mentioned in the original post. Subsequent re-deployments will fail because encryption cannot be removed from a Service Bus once it is granted. Even if it did work, I would be performing unnecessary steps to remove encryption and add it back on every deployment. The same problem exists for API Management, I want to add custom domains but they require Key Vault. I can't re-deploy my ARM template without removing the custom domains when the init step gets repeated. – siegelc Mar 27 '21 at 02:32
  • If you need to redeploy an ARM template, having separate ones for non-service bus resources might be a safer option if incremental deployment with encryption enabled but not specified fails. Perhaps you could add a `condition` to the top-level resource to avoid re-deploying if it already exists. – Heath Mar 28 '21 at 05:52
  • There's no condition to check whether a resource exists or not in an ARM template - https://feedback.azure.com/forums/281804-azure-resource-manager/suggestions/38217556-introduce-function-to-check-whether-a-resource-exi In addition, any components that have key vault integrations have this problem, not just service bus. API management, event hub, service bus, storage account, none of them can be safely re-deployed via ARM template if they are integrated with key vault due to this circular dependency. So this is really not viable as IAC unless I am missing a solution here. – siegelc Mar 29 '21 at 12:53
  • The only link I could find about using ARM deployments for situations like this on learn.microsoft.com recommended using two separate templates, but even then didn't seem to imply subsequent, incremental updates were supported. A scripting solution if you need to redeploy may be the only option. It's a chicken-or-egg problem. If you only care about initial deployment using a single ARM template file, the above solution will work. There may not be a similar solution if you want to incrementally apply that same template. – Heath Mar 31 '21 at 20:43
  • 1
    User-assigned managed identities solves the issue since they can be created and granted access to key vault prior to the deployment of the resource itself. Unfortunately not supported for service bus yet, although it solved the same problem I was having for API management. – siegelc May 13 '21 at 19:54