Azure Prerequisites
This guide provides step-by-step instructions for configuring all Azure prerequisites
required by the
The following prerequisites must be completed before using the <no value>_tenancy_azure resource:
- Create a Service Principal
- Grant Microsoft Graph API Permissions
- Assign Microsoft Entra ID Roles (optional)
- Configure Azure RBAC Permissions
- Configure Billing Account Permissions
1. Create a Service Principal
The credentials block of the <no value>_tenancy_azure resource.
Using the Azure Portal
- Navigate to Microsoft Entra ID > App registrations > New registration
- Set the following values:
- Name: a descriptive name (e.g.
app-volocloud-platform-prod) - Supported Account Types: "Accounts in this organizational directory only (single-tenant)"
- Redirect URI: leave blank
- Name: a descriptive name (e.g.
- Click Register
- Note the Application (client) ID and Directory (tenant) ID from the overview page
Generate a Client Secret
- In the App Registration, navigate to Certificates & secrets > Client secrets
- Click New client secret
- Enter a description and select an expiry period
- Click Add
- Copy the Value immediately — it will not be shown again
Warning
Store the client secret securely. If lost, you will need to generate a new one.
Using the Azure CLI
az ad app create --display-name "app-volocloud-platform-prod"
Note the appId from the output, then create the Service Principal:
az ad sp create --id <appId>
Create a client secret:
az ad app credential reset --id <appId> --append
The output provides:
| Output Field | Volocloud Credential |
|---|---|
appId |
client_id |
password |
client_secret |
tenant |
tenant_id |
2. Grant Microsoft Graph API Permissions
The
Required Permissions
| Permission | Type | Purpose |
|---|---|---|
Application.ReadWrite.OwnedBy |
Application | Manage app registrations that this app creates or owns |
Directory.Read.All |
Application | Read directory data |
Group.ReadWrite.All |
Application | Manage security groups |
Policy.Read.All |
Application | Read policies |
Policy.ReadWrite.ApplicationConfiguration |
Application | Manage application configuration policies |
User.Read |
Delegated | Sign in and read user profile |
Using the Azure Portal
- Navigate to Microsoft Entra ID > App registrations > select your app
- Go to API permissions > Add a permission
- Select Microsoft Graph > Application permissions
- Add each permission listed above
- Click Grant admin consent for [your tenant]
Note
Admin consent MUST be granted by a Global Administrator or Privileged Role Administrator.
Using the Azure CLI
# Get the Microsoft Graph Service Principal object ID
GRAPH_SP_ID=$(az ad sp list --filter "appId eq '00000003-0000-0000-c000-000000000000'" \
--query "[0].id" -o tsv)
# Get your app's Service Principal object ID
APP_SP_ID=$(az ad sp list --filter "appId eq '<your-app-client-id>'" \
--query "[0].id" -o tsv)
# Required Application permission IDs (Microsoft Graph)
# Application.ReadWrite.OwnedBy: 18a4783c-866b-4cc7-a460-3d5e5662c884
# Directory.Read.All: 7ab1d382-f21e-4acd-a863-ba3e13f7da61
# Group.ReadWrite.All: 62a82d76-70ea-41e2-9197-370581804d09
# Policy.Read.All: 246dd0d5-5bd0-4def-940b-0421030a5b68
# Policy.ReadWrite.ApplicationConfiguration: be74164b-cff1-491c-8741-e671cb536e13
PERMISSIONS=(
"18a4783c-866b-4cc7-a460-3d5e5662c884"
"7ab1d382-f21e-4acd-a863-ba3e13f7da61"
"62a82d76-70ea-41e2-9197-370581804d09"
"246dd0d5-5bd0-4def-940b-0421030a5b68"
"be74164b-cff1-491c-8741-e671cb536e13"
)
for PERM_ID in "${PERMISSIONS[@]}"; do
az rest --method post \
--url "https://graph.microsoft.com/v1.0/servicePrincipals/${APP_SP_ID}/appRoleAssignments" \
--body "{
\"principalId\": \"${APP_SP_ID}\",
\"resourceId\": \"${GRAPH_SP_ID}\",
\"appRoleId\": \"${PERM_ID}\"
}"
done
# Grant delegated User.Read permission (admin consent)
# This is done via the Azure Portal: API permissions > Add > Microsoft Graph > Delegated > User.Read > Grant admin consent
3. Assign Microsoft Entra ID Roles (Optional)
If using Microsoft Entra Domain Services, the Service Principal requires the following Entra ID built-in directory roles:
| Role | Purpose |
|---|---|
| Application Administrator | Manage app registrations and enterprise apps |
| Groups Administrator | Manage group creation and membership |
Using the Azure Portal
- Navigate to Microsoft Entra ID > Roles and administrators
- Search for Application Administrator, click on it
- Click Add assignments > search for your Service Principal > Add
- Repeat for Groups Administrator
Using the Azure CLI
APP_SP_ID=$(az ad sp list --filter "appId eq '<your-app-client-id>'" \
--query "[0].id" -o tsv)
# Application Administrator role template ID
az rest --method post \
--url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
--body "{
\"principalId\": \"${APP_SP_ID}\",
\"roleDefinitionId\": \"9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3\",
\"directoryScopeId\": \"/\"
}"
# Groups Administrator role template ID
az rest --method post \
--url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
--body "{
\"principalId\": \"${APP_SP_ID}\",
\"roleDefinitionId\": \"fdd7a751-b60b-444a-984c-02652fe8fa1c\",
\"directoryScopeId\": \"/\"
}"
Note
These roles are only required if microsoft_entra_domain_services is enabled in the
tenancy configuration. If not using Entra Domain Services, this step can be skipped.
4. Configure Azure RBAC Permissions
The
Required Pre-Configured Roles
| Role | Scope | Purpose |
|---|---|---|
| Management Group Contributor | Tenant Root Group | Create and manage the Management Group hierarchy |
| Reader | Subscription (used in subscription_id) |
Azure Resource Manager provider context |
Warning
Without Management Group Contributor at the Tenant Root Group, the provider will fail
with AuthorizationFailed errors when attempting to create management groups.
Without Reader on the subscription, the Azure Resource Manager provider cannot
validate the subscription during initialization.
Using the Azure Portal
Management Group Contributor
- Navigate to Management groups in the Azure Portal
- Select the Tenant Root Group (the top-level management group)
- Click Access control (IAM) > Add > Add role assignment
- Select Management Group Contributor role
- Under Members, search for and select your Service Principal
- Click Review + assign
Subscription Reader
- Navigate to Subscriptions > select the subscription used in
subscription_id - Click Access control (IAM) > Add > Add role assignment
- Select Reader role
- Under Members, search for and select your Service Principal
- Click Review + assign
Using the Azure CLI
# Assign Management Group Contributor at the Tenant Root Group
# The Tenant Root Group ID is the same as your Azure AD Tenant ID
TENANT_ROOT_GROUP_ID="<tenant-id>"
SERVICE_PRINCIPAL_APP_ID="<service-principal-app-id>"
az role assignment create \
--assignee "${SERVICE_PRINCIPAL_APP_ID}" \
--role "Management Group Contributor" \
--scope "/providers/Microsoft.Management/managementGroups/${TENANT_ROOT_GROUP_ID}"
# Assign Reader on the subscription used for subscription_id
az role assignment create \
--assignee "${SERVICE_PRINCIPAL_APP_ID}" \
--role "Reader" \
--scope "/subscriptions/<subscription-id>"
Management Group Creation Restrictions
By default, any principal in an Azure tenant can create Management Groups under the Tenant Root Group. However, some organizations enable the "Require permissions to create new management groups" setting, which restricts Management Group creation to principals with explicit write access on the Tenant Root Group.
If this setting is enabled in your tenant, a Global Administrator must first elevate their access before granting the roles above:
- In the Azure Portal, navigate to Microsoft Entra ID > Properties
- Under Access management for Azure resources, set the toggle to Yes
- Click Save
- Assign the roles listed above
- The Global Administrator can disable the elevated access toggle after granting the role assignments
Note
The Tenant Root Group ID is the same as your Azure AD Tenant ID.
Additional RBAC for MPA (CSP) Billing Accounts
For MPA billing accounts where subscriptions are pre-provisioned by the CSP partner, the Service Principal additionally requires Owner role on each provisioned subscription. For EA and MCA billing accounts, the Service Principal creates subscriptions via the billing API and automatically becomes Owner of those subscriptions.
# Assign Owner on each subscription (MPA only)
az role assignment create \
--assignee "<service-principal-app-id>" \
--role "Owner" \
--scope "/subscriptions/<subscription-id>"
5. Configure Billing Account Permissions
The
Enterprise Agreement (EA)
For EA billing accounts, the Service Principal requires two billing roles:
| Role | Scope | Purpose |
|---|---|---|
| Enrollment account subscription creator | Enrollment Account | Create subscriptions |
| Enrollment reader | Enrollment (billing account) | View cost data, manage budgets at Management Group scope |
Note
These roles are not standard Azure RBAC roles. They are EA billing-specific roles that must be assigned via the REST API. They are not visible in the Azure Portal.
Step 1: Create a Department (Azure Portal)
- Sign in to the Azure Portal as an Enterprise Administrator
- Navigate to Cost Management + Billing > select your EA billing account
- Go to Departments > Add
- Enter a department name (e.g.
Cloud Platform) - Optionally set a department administrator
- Click Save
Step 2: Create an Enrollment Account (Azure Portal)
- In the EA billing account, navigate to Accounts > Add
- Set the following:
- Account name: a descriptive name (e.g.
Volo Cloud Foundations) - Account owner email: a service account email or shared mailbox
- Department: select the department created above
- Dev/Test: enable dev/test subscriptions to allow creation of Dev/Test subscriptions
- Account name: a descriptive name (e.g.
- Click Add
Note
The Account Owner email will receive an activation email. The account must be activated before subscriptions can be created under it. Once activated, note down the Enrollment Account ID (visible in the account details).
Step 3: Identify Your Billing IDs
You will need:
- Billing Account ID (Enrollment ID): visible in Cost Management + Billing > Properties
- Enrollment Account ID: visible under Accounts in the EA billing account
Alternatively, use the Azure CLI:
# Get the billing account (enrollment) ID
az billing account list \
--query "[?agreementType=='EnterpriseAgreement'].{name:name, displayName:displayName}" \
-o table
# Get enrollment accounts
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/enrollmentAccounts?api-version=2024-04-01" \
--query "value[].{name:name, displayName:properties.displayName, owner:properties.accountOwner}" \
-o table
Step 4: Assign Subscription Creator Role to the Service Principal
This must be done by the Enrollment Account Owner (or an EA Administrator) using the REST API.
First, get the Service Principal's Object ID (not the App ID):
az ad sp list --display-name "<service-principal-name>" \
--query "[].{displayName:displayName, objectId:id}" -o table
Generate a unique GUID for the role assignment:
# macOS/Linux
python3 -c "import uuid; print(uuid.uuid4())"
# PowerShell
[guid]::NewGuid()
Assign the Subscription Creator role on the Enrollment Account:
az rest --method put \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/enrollmentAccounts/<enrollment-account-id>/billingRoleAssignments/<new-guid>?api-version=2019-10-01-preview" \
--body '{
"properties": {
"principalId": "<service-principal-object-id>",
"principalTenantId": "<tenant-id>",
"roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/enrollmentAccounts/<enrollment-account-id>/billingRoleDefinitions/a0bcee42-bf30-4d1b-926a-48d21664ef71"
}
}'
A 200 OK response confirms the role was assigned successfully.
Step 5: Assign Enrollment Reader Role to the Service Principal
This role is required for the Service Principal to manage budgets at the Management Group scope. Without it, budget operations at MG-level will silently fail on EA billing accounts.
This must be done by a user with the Enrollment Administrator (enrollment writer) role.
Generate a new unique GUID and assign the Enrollment Reader role at the Enrollment level:
az rest --method put \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingRoleAssignments/<new-guid>?api-version=2019-10-01-preview" \
--body '{
"properties": {
"principalId": "<service-principal-object-id>",
"principalTenantId": "<tenant-id>",
"roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingRoleDefinitions/24f8edb6-1668-4659-b5e2-40bb5f3a7d7e"
}
}'
A 200 OK response confirms the role was assigned successfully.
Verify EA Role Assignments
To verify the roles were assigned correctly:
# Check enrollment-level roles
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingRoleAssignments?api-version=2024-04-01" \
-o json
# Check enrollment account-level roles
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/enrollmentAccounts/<enrollment-account-id>/billingRoleAssignments?api-version=2024-04-01" \
-o json
EA Billing Role Assignments
- Each role assignment requires a unique GUID as the assignment name
- The
principalIdmust be the Service Principal's Object ID from Enterprise Applications, NOT the Application (client) ID from App Registrations - These role assignments are not visible in the Azure Portal — they can only be managed via REST API
- A Service Principal can have only one role per scope — assigning a new role at the same scope replaces the previous one
EA Configuration Reference
Once the billing permissions are configured, use the following values in your
<no value>_tenancy_azure resource:
billing = {
account_type = "ea"
ea = {
account_id = "<billing-account-id>" # The EA enrollment number
enrollment_id = "<enrollment-account-id>" # The enrollment account under the EA
}
}
Microsoft Customer Agreement (MCA)
For MCA billing accounts, the Service Principal requires the Billing profile contributor role on a dedicated Billing Profile. This allows the provider to create subscriptions across all invoice sections under the profile and supports future management of invoice sections.
Step 1: Identify Your Billing IDs
You will need:
- Billing Account ID: visible in Cost Management + Billing > Properties
- Billing Profile ID: visible under Billing profiles
- Invoice Section ID: visible under the billing profile's Invoice sections
Using the Azure CLI:
# List MCA billing accounts
az billing account list \
--query "[?agreementType=='MicrosoftCustomerAgreement'].{name:name, displayName:displayName}" \
-o table
# List billing profiles
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingProfiles?api-version=2024-04-01" \
--query "value[].{name:name, displayName:properties.displayName}" \
-o table
# List invoice sections
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingProfiles/<billing-profile-id>/invoiceSections?api-version=2024-04-01" \
--query "value[].{name:name, displayName:properties.displayName}" \
-o table
Step 2: Assign Billing Permissions
Grant the Service Principal Billing profile contributor on the Billing Profile:
- Navigate to Cost Management + Billing > select your MCA billing account
- Go to Billing profiles > select the billing profile
- Click Access control (IAM) > Add
- Select Billing profile contributor role
- Search for and select your Service Principal
- Click Save
Alternatively, use the Azure CLI:
az rest --method put \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingProfiles/<billing-profile-id>/billingRoleAssignments/<new-guid>?api-version=2024-04-01" \
--body '{
"properties": {
"principalId": "<service-principal-object-id>",
"principalTenantId": "<tenant-id>",
"roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingProfiles/<billing-profile-id>/billingRoleDefinitions/40000000-aaaa-bbbb-cccc-100000000001"
}
}'
Note
For MCA billing accounts, budget operations at both subscription and Management Group scope work with standard Azure RBAC (the Service Principal automatically becomes Owner of subscriptions it creates) without additional billing roles.
MCA Configuration Reference
Once the billing permissions are configured, use the following values in your
<no value>_tenancy_azure resource:
billing = {
account_type = "mca"
mca = {
account_id = "<billing-account-id>"
profile_id = "<billing-profile-id>"
invoice_id = "<invoice-section-id>"
}
}
MCA Subscription Creation Limits
Microsoft imposes rate limits on subscription creation for MCA accounts purchased directly through Azure.com:
- Maximum 5 subscriptions per MCA billing account
- 1 subscription creation per 24 hours (cooling period between creations)
This means provisioning all 4 platform subscriptions (connectivity, identity, management, security) for a new customer could require up to 4 days — which is not practical for production deployments.
Checking Your MCA Billing Relationship Type
Before onboarding, verify the billing profile's relationship type:
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingProfiles?api-version=2020-05-01" \
--query "value[].{name:name, displayName:properties.displayName, relationshipType:properties.billingRelationshipType}" \
-o table
Direct— purchased through Azure.com (subject to 1/day subscription creation limit)IndirectCustomer— purchased through a Microsoft representative (no limit)
Action Required for Azure.com MCA Accounts
If your MCA billing account was purchased directly through Azure.com (Direct relationship
type), you must raise a support ticket with Microsoft to have the subscription creation
limit increased before onboarding. Request the following:
- Remove or increase the cooling period restriction (at least 5 subscriptions per day)
- Increase the maximum subscription limit to 50 (default is 5)
Alternatively, convert your billing account to an MCA purchased through a Microsoft representative — these do not have the 1-per-day restriction.
We do not support MCA accounts with the 1-per-day subscription creation limit for automated deployments. The tenancy template creates all platform subscriptions in a single apply and will fail if rate-limited.
For more details, see: Microsoft documentation on subscription creation limits
Microsoft Partner Agreement (MPA)
For MPA (CSP) billing accounts, subscriptions are managed by the Cloud Solution Provider partner. The CSP partner must grant the Service Principal appropriate permissions on the subscriptions they provision.
MPA Billing Limitation
MPA (CSP) billing accounts do not support Management Group-level budgets. Budgets can
only be created at the individual subscription scope. The budgets.root configuration
block is not supported for MPA billing accounts.
Prerequisites
- The CSP partner provisions the core subscriptions (connectivity, identity, management, security)
- The CSP partner grants the Service Principal Owner role on each provisioned subscription
- The Service Principal must also have Owner at the root Management Group level (as described in Configure Azure RBAC Permissions)
Step 1: CSP Partner Grants Subscription Access
The CSP partner must assign Owner on each subscription to the Service Principal. This is done by the partner through their Partner Center or via the Azure Portal/CLI on the customer's tenant:
# For each subscription provisioned by the CSP
az role assignment create \
--assignee "<service-principal-app-id>" \
--role "Owner" \
--scope "/subscriptions/<subscription-id>"
Note
The CSP partner must perform this step, or delegate Admin on Behalf Of (AOBO) access so that an administrator in the customer tenant can assign the role.
Step 2: Provide Existing Subscription IDs
Since MPA billing accounts use pre-provisioned subscriptions, you must provide the existing subscription IDs in the configuration rather than having the provider create them.
MPA Configuration Reference
Use the following values in your <no value>_tenancy_azure resource:
billing = {
account_type = "mpa"
existing = {
connectivity_subscription_id = "<connectivity-subscription-id>"
identity_subscription_id = "<identity-subscription-id>"
management_subscription_id = "<management-subscription-id>"
security_subscription_id = "<security-subscription-id>"
}
}
Budgets for MPA billing accounts are only supported at the subscription level:
subscriptions = {
connectivity = {
abbreviation = "conn"
budgets = [
{
amount = 2000
notifications = [
{
contact_emails = ["alerts@example.com"]
threshold = 90
}
]
}
]
# ... other configuration ...
}
}
Summary of Required Permissions
| Component | Permission | Scope | Required |
|---|---|---|---|
| Azure RBAC | Management Group Contributor | Tenant Root Group | Yes |
| Azure RBAC | Reader | Subscription (used in subscription_id) |
Yes |
| Azure RBAC | Owner | Each provisioned subscription | If billing type is MPA |
| Microsoft Graph API | Application.ReadWrite.OwnedBy | Tenant | Yes |
| Microsoft Graph API | Directory.Read.All | Tenant | Yes |
| Microsoft Graph API | Group.ReadWrite.All | Tenant | Yes |
| Microsoft Graph API | Policy.Read.All | Tenant | Yes |
| Microsoft Graph API | Policy.ReadWrite.ApplicationConfiguration | Tenant | Yes |
| Microsoft Graph API | User.Read (Delegated) | Tenant | Yes |
| Entra ID Role | Application Administrator | Tenant | If using Entra Domain Services |
| Entra ID Role | Groups Administrator | Tenant | If using Entra Domain Services |
| EA Billing | Subscription Creator | Enrollment Account | If billing type is EA |
| EA Billing | Enrollment Reader | Enrollment (billing account) | If billing type is EA |
| MCA Billing | Billing profile contributor | Billing Profile | If billing type is MCA |
| MPA Billing | Owner | Each provisioned subscription | If billing type is MPA |
Configuring the Provider
Once all prerequisites are in place, configure the <no value>_tenancy_azure resource credentials:
resource "<no value>_tenancy_azure" "example" {
# ... configuration ...
credentials = {
client_id = "<application-client-id>"
client_secret = "<client-secret-value>"
subscription_id = "<management-subscription-id>"
tenant_id = "<directory-tenant-id>"
}
}
Warning
Do not hard-code credentials in your Terraform configuration. Use Terraform variables, environment variables, or a secrets manager to provide sensitive values.
The subscription_id should reference an existing subscription where the Service Principal
has Reader access. This subscription is used as the default context for Azure Resource Manager
operations but no resources are deployed into it directly. The provider is configured with
resource_provider_registrations = "none" so no provider registration permissions are required
on this subscription.
Troubleshooting
EA Budgets Not Working
If budget creation succeeds but the budget does not appear in the Azure Portal or subsequent reads return 404, the Service Principal is likely missing the Enrollment Reader role at the enrollment level. The Azure Consumption API may accept the request but fail to persist the budget without this billing role.
Fix: Assign the Enrollment Reader role as described in Step 5.
Service Principal Object ID vs Application ID
A common error when assigning EA billing roles is using the Application (client) ID instead of the Service Principal Object ID. These are different values:
- Application (client) ID: found in App Registrations — used for authentication
- Service Principal Object ID: found in Enterprise Applications — used for role assignments
To find the correct Object ID:
az ad sp list --display-name "<app-name>" --query "[].{name:displayName, objectId:id}" -o table
Admin Consent Not Granted
If the provider fails with Graph API permission errors, verify that admin consent was granted for all required permissions. Navigate to the App Registration > API permissions and check that each permission shows a green checkmark under "Status".