Azure
CloudPlans authenticates with a service principal (Tenant ID, Client ID and Client Secret) that you create in your subscription. Its calls come from the CloudPlans worker hosted in AWS account 605134468875.
The console generates it for you: in Credentials → Add Credential → Azure, click Helper: Azure Cloud Shell setup, tick only the services you use and run the script in Azure Cloud Shell (Bash). It creates a custom role with only the actions below, scoped to one resource group (not the broad Contributor role), assigns it to a new service principal valid for 2 years and prints the values to paste in the credential (Create).
Permissions per service
| Service | RBAC actions needed |
|---|---|
| Azure VM (Stoppable) | Microsoft.Compute/virtualMachines/readMicrosoft.Compute/virtualMachines/instanceView/readMicrosoft.Compute/virtualMachines/start/actionMicrosoft.Compute/virtualMachines/deallocate/action |
| Azure VM (Vertical Resize) | Microsoft.Compute/virtualMachines/readMicrosoft.Compute/virtualMachines/instanceView/readMicrosoft.Compute/virtualMachines/start/actionMicrosoft.Compute/virtualMachines/deallocate/actionMicrosoft.Compute/virtualMachines/write |
| Azure Service Plan (Scalable) | Microsoft.Web/serverfarms/readMicrosoft.Web/serverfarms/write |
| Azure AKS (Scalable) | Microsoft.ContainerService/managedClusters/readMicrosoft.ContainerService/managedClusters/agentPools/readMicrosoft.ContainerService/managedClusters/agentPools/write |
| Azure VM Scale Set (Scalable) | Microsoft.Compute/virtualMachineScaleSets/readMicrosoft.Compute/virtualMachineScaleSets/write |
| Azure Container Instance (Stoppable) | Microsoft.ContainerInstance/containerGroups/readMicrosoft.ContainerInstance/containerGroups/start/actionMicrosoft.ContainerInstance/containerGroups/stop/action |
| Azure Application Gateway (Stoppable) | Microsoft.Network/applicationGateways/readMicrosoft.Network/applicationGateways/start/actionMicrosoft.Network/applicationGateways/stop/action |
| Azure Synapse SQL Pool (Stoppable) | Microsoft.Synapse/workspaces/readMicrosoft.Synapse/workspaces/sqlPools/readMicrosoft.Synapse/workspaces/sqlPools/resume/actionMicrosoft.Synapse/workspaces/sqlPools/pause/action |
| Azure Databricks Cluster (Stoppable) | None: authenticates with a Databricks personal access token |
| Azure Service Bus Premium (Vertical Resize) | Microsoft.ServiceBus/namespaces/readMicrosoft.ServiceBus/namespaces/write |
| Azure Cosmos DB Provisioned (Vertical Resize) | Microsoft.DocumentDB/databaseAccounts/readMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/readMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/readMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/write |
| Azure MySQL Flexible Server (Stoppable) | Microsoft.DBforMySQL/flexibleServers/readMicrosoft.DBforMySQL/flexibleServers/start/actionMicrosoft.DBforMySQL/flexibleServers/stop/action |
| Azure MySQL Flexible Server (Vertical Resize) | Microsoft.DBforMySQL/flexibleServers/readMicrosoft.DBforMySQL/flexibleServers/start/actionMicrosoft.DBforMySQL/flexibleServers/stop/actionMicrosoft.DBforMySQL/flexibleServers/write |
| Azure PostgreSQL Flexible Server (Stoppable) | Microsoft.DBforPostgreSQL/flexibleServers/readMicrosoft.DBforPostgreSQL/flexibleServers/start/actionMicrosoft.DBforPostgreSQL/flexibleServers/stop/action |
| Azure PostgreSQL Flexible Server (Vertical Resize) | Microsoft.DBforPostgreSQL/flexibleServers/readMicrosoft.DBforPostgreSQL/flexibleServers/start/actionMicrosoft.DBforPostgreSQL/flexibleServers/stop/actionMicrosoft.DBforPostgreSQL/flexibleServers/write |
| Azure ML Online Endpoint (Scalable) | Microsoft.MachineLearningServices/workspaces/onlineEndpoints/readMicrosoft.MachineLearningServices/workspaces/onlineEndpoints/deployments/readMicrosoft.MachineLearningServices/workspaces/onlineEndpoints/deployments/write |
The helper scopes the role to the resource group you enter. The connection test and Find Resources list resources across the whole subscription, so if either reports an authorization error, assign the role at subscription scope instead (replace the resource group scope by /subscriptions/<subscription-id>).
Azure Cloud Shell script
# Run this in Azure Cloud Shell (Bash), in the subscription you want CloudPlans to manage.
SUBSCRIPTION_ID="<your-subscription-id>"
RESOURCE_GROUP="<your-resource-group>"
SP_NAME="cloudplans-scheduler"
ROLE_NAME="CloudPlansSchedulerRole"
az account set --subscription "$SUBSCRIPTION_ID"
# Make sure the resource group exists (or use an existing one)
az group show -n "$RESOURCE_GROUP" >/dev/null \
|| az group create -n "$RESOURCE_GROUP" -l westeurope
SCOPE="/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
# Custom role — minimum actions for the resource types you selected:
# Azure VM (Stoppable), Azure VM (Vertical Resize), Azure Service Plan (Scalable), Azure AKS (Scalable), Azure VM Scale Set (Scalable), Azure Container Instance (Stoppable), Azure Application Gateway (Stoppable), Azure Synapse SQL Pool (Stoppable), Azure Databricks Cluster (Stoppable), Azure Service Bus Premium (Vertical Resize), Azure Cosmos DB Provisioned (Vertical Resize), Azure MySQL Flexible Server (Stoppable), Azure MySQL Flexible Server (Vertical Resize), Azure PostgreSQL Flexible Server (Stoppable), Azure PostgreSQL Flexible Server (Vertical Resize), Azure ML Online Endpoint (Scalable)
# (scoped to the resource group only, not the subscription — a real custom
# role instead of the broad built-in "Contributor")
cat > /tmp/cloudplans-role.json <<JSON
{
"Name": "$ROLE_NAME",
"IsCustom": true,
"Description": "CloudPlans least-privilege role for the resource types selected in the console",
"Actions": [
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/instanceView/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/write",
"Microsoft.Web/serverfarms/read",
"Microsoft.Web/serverfarms/write",
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.ContainerService/managedClusters/agentPools/write",
"Microsoft.Compute/virtualMachineScaleSets/read",
"Microsoft.Compute/virtualMachineScaleSets/write",
"Microsoft.ContainerInstance/containerGroups/read",
"Microsoft.ContainerInstance/containerGroups/start/action",
"Microsoft.ContainerInstance/containerGroups/stop/action",
"Microsoft.Network/applicationGateways/read",
"Microsoft.Network/applicationGateways/start/action",
"Microsoft.Network/applicationGateways/stop/action",
"Microsoft.Synapse/workspaces/read",
"Microsoft.Synapse/workspaces/sqlPools/read",
"Microsoft.Synapse/workspaces/sqlPools/resume/action",
"Microsoft.Synapse/workspaces/sqlPools/pause/action",
"Microsoft.ServiceBus/namespaces/read",
"Microsoft.ServiceBus/namespaces/write",
"Microsoft.DocumentDB/databaseAccounts/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/write",
"Microsoft.DBforMySQL/flexibleServers/read",
"Microsoft.DBforMySQL/flexibleServers/start/action",
"Microsoft.DBforMySQL/flexibleServers/stop/action",
"Microsoft.DBforMySQL/flexibleServers/write",
"Microsoft.DBforPostgreSQL/flexibleServers/read",
"Microsoft.DBforPostgreSQL/flexibleServers/start/action",
"Microsoft.DBforPostgreSQL/flexibleServers/stop/action",
"Microsoft.DBforPostgreSQL/flexibleServers/write",
"Microsoft.MachineLearningServices/workspaces/onlineEndpoints/read",
"Microsoft.MachineLearningServices/workspaces/onlineEndpoints/deployments/read",
"Microsoft.MachineLearningServices/workspaces/onlineEndpoints/deployments/write"
],
"NotActions": [],
"AssignableScopes": ["$SCOPE"]
}
JSON
az role definition create --role-definition /tmp/cloudplans-role.json
SP_JSON=$(az ad sp create-for-rbac \
--name "$SP_NAME" \
--role "$ROLE_NAME" \
--scopes "$SCOPE" \
--years 2 -o json)
echo "----- Paste the following in CloudPlans -----"
echo "Tenant ID : $(echo $SP_JSON | jq -r .tenant)"
echo "Subscription ID : $SUBSCRIPTION_ID"
echo "Resource Group : $RESOURCE_GROUP"
echo "Client ID : $(echo $SP_JSON | jq -r .appId)"
echo "Client Secret : $(echo $SP_JSON | jq -r .password)"
echo ""
echo "NOTE: Azure Databricks clusters use a separate Databricks access token"
echo "(not this service principal) -- paste it into the credential's 'key' field instead."
Terraform
variable "subscription_id" {
type = string
}
variable "resource_group" {
type = string
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
data "azurerm_resource_group" "rg" {
name = var.resource_group
}
data "azuread_client_config" "current" {}
resource "azurerm_role_definition" "cloudplans" {
name = "CloudPlansSchedulerRole"
scope = data.azurerm_resource_group.rg.id
description = "CloudPlans least-privilege role"
permissions {
actions = [
"Microsoft.Compute/virtualMachineScaleSets/read",
"Microsoft.Compute/virtualMachineScaleSets/write",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/instanceView/read",
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/write",
"Microsoft.ContainerInstance/containerGroups/read",
"Microsoft.ContainerInstance/containerGroups/start/action",
"Microsoft.ContainerInstance/containerGroups/stop/action",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.ContainerService/managedClusters/agentPools/write",
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.DBforMySQL/flexibleServers/read",
"Microsoft.DBforMySQL/flexibleServers/start/action",
"Microsoft.DBforMySQL/flexibleServers/stop/action",
"Microsoft.DBforMySQL/flexibleServers/write",
"Microsoft.DBforPostgreSQL/flexibleServers/read",
"Microsoft.DBforPostgreSQL/flexibleServers/start/action",
"Microsoft.DBforPostgreSQL/flexibleServers/stop/action",
"Microsoft.DBforPostgreSQL/flexibleServers/write",
"Microsoft.DocumentDB/databaseAccounts/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/write",
"Microsoft.MachineLearningServices/workspaces/onlineEndpoints/deployments/read",
"Microsoft.MachineLearningServices/workspaces/onlineEndpoints/deployments/write",
"Microsoft.MachineLearningServices/workspaces/onlineEndpoints/read",
"Microsoft.Network/applicationGateways/read",
"Microsoft.Network/applicationGateways/start/action",
"Microsoft.Network/applicationGateways/stop/action",
"Microsoft.ServiceBus/namespaces/read",
"Microsoft.ServiceBus/namespaces/write",
"Microsoft.Synapse/workspaces/read",
"Microsoft.Synapse/workspaces/sqlPools/pause/action",
"Microsoft.Synapse/workspaces/sqlPools/read",
"Microsoft.Synapse/workspaces/sqlPools/resume/action",
"Microsoft.Web/serverfarms/read",
"Microsoft.Web/serverfarms/write"
]
not_actions = []
}
assignable_scopes = [data.azurerm_resource_group.rg.id]
}
resource "azuread_application" "cloudplans" {
display_name = "cloudplans-scheduler"
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_service_principal" "cloudplans" {
client_id = azuread_application.cloudplans.client_id
owners = [data.azuread_client_config.current.object_id]
}
resource "time_static" "created" {}
resource "azuread_application_password" "cloudplans" {
application_id = azuread_application.cloudplans.id
end_date = timeadd(time_static.created.rfc3339, "17520h") # 2 years
}
resource "azurerm_role_assignment" "cloudplans" {
scope = data.azurerm_resource_group.rg.id
role_definition_id = azurerm_role_definition.cloudplans.role_definition_resource_id
principal_id = azuread_service_principal.cloudplans.object_id
}
output "tenant_id" {
value = data.azuread_client_config.current.tenant_id
}
output "client_id" {
value = azuread_application.cloudplans.client_id
}
output "client_secret" {
value = azuread_application_password.cloudplans.value
sensitive = true
}