# Azure resource handler for multiple cloud subscriptions

> A single Microsoft Entra ID application can create an Azure resource handler for every subscription it can access

---

Site [llms.txt](/llms.txt)

---

Create one Azure resource handler as described in [Azure](/docs/cmp/configure/infrastructure/resource-handlers/azure/), then import the rest. Cloud Management Platform (CMP) reads the subscriptions the application can access and creates a handler for each one you select, reusing that handler's credentials.

## Create a custom role in Azure

**Contributor** on each subscription covers everything CMP does; see [Required Azure permissions](/docs/cmp/configure/infrastructure/resource-handlers/azure/required-permissions/). To grant less than that, create a custom role with the permissions CMP uses and assign it.

1. Log in to your Azure portal and select the target subscription.
1. From the left menu, select **Access control (IAM)**.
1. Select **Add custom role**.
1. On the **Basics** tab, enter a name and description for the role.
1. Under **Baseline permissions**, select **Start from scratch**.
1. Leave the **Permissions** and **Assignable scopes** tabs as they are.
1. On the **JSON** tab, select **Edit** and add the following entries to the **actions** array:

   ```json
   "*/read",
   "Microsoft.Cache/redis/write",
   "Microsoft.Compute/*/delete",
   "Microsoft.Compute/*/write",
   "Microsoft.Compute/virtualMachines/*/action",
   "Microsoft.ContainerService/managedClusters/write",
   "Microsoft.DBforMariaDB/servers/*/action",
   "Microsoft.DBforMariaDB/servers/databases/write",
   "Microsoft.DBforMariaDB/servers/databases/delete",
   "Microsoft.DBforMariaDB/servers/delete",
   "Microsoft.DBforMariaDB/servers/write",
   "Microsoft.DBforMySQL/flexibleServers/delete",
   "Microsoft.DBforMySQL/flexibleServers/write",
   "Microsoft.DBforMySQL/servers/*/action",
   "Microsoft.DBforMySQL/servers/databases/write",
   "Microsoft.DBforMySQL/servers/databases/delete",
   "Microsoft.DBforMySQL/servers/delete",
   "Microsoft.DBforMySQL/servers/write",
   "Microsoft.DBforPostgreSQL/servers/*/action",
   "Microsoft.DBforPostgreSQL/servers/databases/write",
   "Microsoft.DBforPostgreSQL/servers/databases/delete",
   "Microsoft.DBforPostgreSQL/servers/delete",
   "Microsoft.DBforPostgreSQL/servers/write",
   "Microsoft.DataFactory/factories/write",
   "Microsoft.DataFactory/factories/delete",
   "Microsoft.Databricks/workspaces/write",
   "Microsoft.Databricks/workspaces/delete",
   "Microsoft.DocumentDB/databaseAccounts/write",
   "Microsoft.DocumentDB/databaseAccounts/delete",
   "Microsoft.Network/*/delete",
   "Microsoft.Network/*/write",
   "Microsoft.Network/networkInterfaces/write",
   "Microsoft.Network/networkInterfaces/delete",
   "Microsoft.Network/networkInterfaces/join/action",
   "Microsoft.Network/networkSecurityGroups/write",
   "Microsoft.Network/networkSecurityGroups/delete",
   "Microsoft.Network/networkSecurityGroups/*/action",
   "Microsoft.Network/virtualNetworks/subnets/write",
   "Microsoft.Network/virtualNetworks/write",
   "Microsoft.Network/virtualNetworks/delete",
   "Microsoft.Resources/deployments/write",
   "Microsoft.Resources/subscriptions/resourceGroups/write",
   "Microsoft.Resources/subscriptions/resourceGroups/delete",
   "Microsoft.Sql/servers/databases/*/action",
   "Microsoft.Sql/servers/databases/delete",
   "Microsoft.Sql/servers/databases/write",
   "Microsoft.Sql/servers/delete",
   "Microsoft.Sql/servers/write",
   "Microsoft.Storage/storageAccounts/delete",
   "Microsoft.Storage/storageAccounts/listKeys/action",
   "Microsoft.Storage/storageAccounts/write",
   "Microsoft.Web/serverfarms/write",
   "Microsoft.Web/kubeEnvironments/read",
   "Microsoft.Kubernetes/connectedClusters/Read",
   "Microsoft.Kubernetes/RegisteredSubscriptions/read",
   "Microsoft.Kubernetes/connectedClusters/write",
   "Microsoft.Kubernetes/connectedClusters/delete",
   "Microsoft.Cache/redis/delete",
   "Microsoft.DesktopVirtualization/applicationgroups/read",
   "Microsoft.DesktopVirtualization/applicationgroups/*/read",
   "Microsoft.DesktopVirtualization/hostpools/read",
   "Microsoft.DesktopVirtualization/hostpools/*/read",
   "Microsoft.DesktopVirtualization/workspaces/read",
   "Microsoft.DesktopVirtualization/workspaces/*/read",
   "Microsoft.Advisor/recommendations/read",
   "Microsoft.Consumption/reservationRecommendations/read",
   "Microsoft.Consumption/reservationRecommendationDetails/read",
   "Microsoft.Network/virtualNetworks/subnets/*/action",
   "Microsoft.Compute/virtualMachines/write",
   "Microsoft.DataFactory/locations/configureFactoryRepo/action",
   "Microsoft.Web/sites/Write",
   "Microsoft.Storage/storageAccounts/blobServices/containers/write",
   "Microsoft.Storage/storageAccounts/blobServices/containers/delete",
   "Microsoft.Storage/storageAccounts/blobServices/containers/read",
   "Microsoft.ContainerService/managedClusters/listClusterAdminCredential/action",
   "Microsoft.ContainerService/managedClusters/delete",
   "Microsoft.ContainerService/managedClusters/read"
   ```

1. Select **Save**, then on the **Review + create** tab, select **Update**.

## Assign the role with PowerShell

To avoid repeating the portal steps on each subscription, create and assign the role from Azure Cloud Shell instead.

### Prerequisites

- An Azure account with PowerShell access that can create and assign roles on each subscription, such as **Owner** or **User Access Administrator**.
- A registered application in Azure for CMP to authenticate as.
- At least one active Azure subscription.

### Steps

1. Log in to your Azure portal and select **Cloud Shell** in the top navigation bar.
1. In the Cloud Shell window, select **PowerShell**.
1. Create the custom role across every subscription. The command prompts for a location and a role name.

   ```powershell
   $location = Read-Host -Prompt "Enter a location (i.e. centralus)"
   $roleName = Read-Host -Prompt "Enter custom role Name"
   $templateUri = "https://raw.githubusercontent.com/CloudBoltSoftware/cloudbolt-forge/master/internal/Azure/customroledefinition.json"
   $scopes = foreach($item in Get-AzSubscription){ "/subscriptions/$($item.Id)" }
   New-AzDeployment -Location $location -TemplateUri $templateUri -scopes $scopes -roleName $roleName
   ```

1. Get the application ID. Replace `<applicationName>` with your application's name.

   ```powershell
   $appID = (Get-AzADServicePrincipal -DisplayName <applicationName>).id
   ```

1. Assign the role to the application on every subscription.

   ```powershell
   foreach($item in Get-AzSubscription)
   {
   New-AzRoleAssignment -ObjectId $appID -RoleDefinitionName $roleName -Scope "/subscriptions/$($item.Id)"
   }
   ```

## Import subscriptions

**Import Subscriptions** appears on an Azure resource handler's **Overview** tab when the application that handler authenticates as can see more than one subscription.

1. Go to **Admin** > **Resource Handlers**.
1. Select an Azure resource handler that uses the application whose subscriptions you want to import.
1. On the **Overview** tab, select **Import Subscriptions**.
1. In **Subscriptions**, select the subscriptions to import. The list omits subscriptions that already have a handler using the same Application ID.
1. In **Import locations**, select the regions to import. CMP creates one environment per region on each new handler.
1. Select **Import Subscriptions**.

CMP queues an **Import Subscriptions** job. When you select more than one subscription, it runs one sub-job per subscription, so handlers appear as the job progresses. Each takes its name from the subscription and reuses the original handler's Application ID, Authentication Key, Directory ID, cloud environment, and Auth Type.

<div class="alert alert-info" role="alert">


If every subscription already has a handler, the dialog says there is nothing to import and offers no submit button.
</div>


## Related

- [Azure](/docs/cmp/configure/infrastructure/resource-handlers/azure/): create the first Azure resource handler, and the Azure roles CMP needs to create servers.
- [Environments](/docs/cmp/configure/infrastructure/environments/): what CMP creates for each imported region.
