This guide walks you through connecting an Azure subscription using a Service Principal with client credentials. After setup, the orchestrator can deploy resources into your subscription using short-lived tokens acquired via the client credentials flow.
What this does
After setup, the orchestrator can deploy resources into your Azure subscription by:
- Authenticating to Azure AD using your Service Principal’s client ID + client secret (the standard client credentials grant)
- Receiving a short-lived bearer token scoped to Azure Resource Manager
- Using that token to call Azure ARM APIs for your deployments
Prerequisites
- An Azure subscription with Owner or User Access Administrator role (so you can grant role assignments)
- Azure AD permissions to create app registrations (any user can by default; some tenants restrict this)
azCLI installed and authenticated to that tenant- 5-10 minutes for first-time setup
Step 1 — Create an Azure AD App Registration
The App Registration is the identity the orchestrator will use to authenticate.
# Create the app registration (also creates a service principal)
az ad app create \
--display-name "AI Orchestrator Deployer" \
--sign-in-audience AzureADMyOrg
# Note the appId from the output (you'll need it):
# {"appId": "11111111-2222-3333-4444-555555555555", ...}
APP_ID="11111111-2222-3333-4444-555555555555" # from the create-output
# Create the service principal associated with this app
az ad sp create --id "$APP_ID"Step 2 — Generate a client secret
# Generate a client secret valid for 1 year
az ad app credential reset \
--id "$APP_ID" \
--years 1 \
--display-name "orchestrator-runtime"
# Output:
# {
# "appId": "11111111-...",
# "password": "very-long-secret-value", ← copy this; shown ONLY ONCE
# "tenant": "abcdef00-...."
# }Copy the password value now. Azure does NOT show it again. If
you lose it, you’ll need to generate a new one and rotate.
Save also:
- App ID (
appId) - Tenant ID (
tenant) - Client secret (
password)
You’ll paste all three into the orchestrator’s connect form.
Step 3 — Grant the Service Principal a role on your subscription
By default, the SP can authenticate but can’t do anything. Grant it roles via Azure RBAC:
# Get your subscription ID
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
echo "Subscription: $SUBSCRIPTION_ID"
# Grant Contributor on the subscription
# (or scope tighter to a specific resource group — see below)
az role assignment create \
--assignee "$APP_ID" \
--role "Contributor" \
--scope "/subscriptions/$SUBSCRIPTION_ID"Contributor at subscription scope is broad. Tighten by creating a dedicated resource group (az group create --name orchestrator-managed --location eastus) and scoping the role assignment to that RG instead of the full subscription. Then the orchestrator can only deploy into orchestrator-managed — blast radius limited if anything goes wrong.Step 4 — Paste the values into the orchestrator UI
In /cloud-accounts → Connect cloud → Azure:
| Field | Value |
|---|---|
| Account label | Free-text, e.g. prod-azure |
| Tenant ID | From Step 2 output (abcdef00-...) |
| Subscription ID | The Azure subscription ID (SUBSCRIPTION_ID above) |
| Client ID | App ID from Step 1 |
| Client Secret | The password from Step 2 (one-time-visible value) |
| Default region | Azure region (e.g., eastus, westeurope) |
Click Connect. The orchestrator runs the probe:
- Calls Azure AD with client credentials → receives a token
- Calls Azure ARM
subscriptions/{id}API as the smoke test - Marks the cloud account
connected(green badge)
Verify
# As an Azure admin:
az role assignment list \
--assignee "$APP_ID" \
--all \
--query '[].{scope:scope,role:roleDefinitionName}' \
--output tableShould show your Contributor (or whatever role) assignment with
the scope you set.
Troubleshooting
”AADSTS7000215: Invalid client secret”
The client secret pasted into the form doesn’t match what Azure issued in Step 2. Either:
- Typo when pasting
- The secret was rotated/regenerated after you copied it
- The secret expired (max 2 years, default 1 year)
Generate a new secret per Step 2 and re-paste.
”AuthorizationFailed: … does not have authorization to perform action X”
The Service Principal authenticated successfully, but lacks the specific Azure ARM permission. Either:
- The role isn’t granted at the scope you’re deploying into (e.g., granted on a different RG)
- The role doesn’t include the action (e.g.,
Contributordoesn’t includeMicrosoft.Authorization/roleAssignments/write)
Add the role at the right scope:
az role assignment create \
--assignee "$APP_ID" \
--role "ROLE_THAT_INCLUDES_THE_ACTION" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/THE_RG"“TenantNotFound” or “Subscription cannot be found”
Tenant ID or subscription ID typo. Verify:
az account show --query "{tenantId:tenantId, subscriptionId:id}"Provider registration errors during deployment
Some Azure resource providers must be registered per subscription
before they can be used (e.g., Microsoft.ContainerService for AKS):
# Register the providers your deployments need
for provider in \
Microsoft.Compute \
Microsoft.Network \
Microsoft.Storage \
Microsoft.ContainerService \
Microsoft.DBforPostgreSQL; do
az provider register --namespace "$provider"
doneThese take 1-5 minutes to register.
Removing an Azure connection
# 1. Remove the cloud-account row from the orchestrator UI
# 2. Remove role assignments
az role assignment delete \
--assignee "$APP_ID" \
--scope "/subscriptions/$SUBSCRIPTION_ID"
# 3. Delete the app registration (also deletes the SP + secret)
az ad app delete --id "$APP_ID"How this works under the hood
┌─────────────────────────┐
│ Orchestrator backend │
└───────────┬─────────────┘
│ POST login.microsoftonline.com/<tenant>/oauth2/v2.0/token
│ grant_type=client_credentials
│ client_id=<app_id>
│ client_secret=<secret>
│ scope=https://management.azure.com/.default
▼
┌─────────────────────────┐
│ Azure AD │
│ - validates client │
│ - returns 1h bearer │
└───────────┬─────────────┘
│ Bearer eyJ0eXAiOiJKV1QiLC...
▼
┌─────────────────────────┐
│ Orchestrator calls ARM │
│ PUT /subscriptions/.../resourceGroups/.../providers/Microsoft.DBforPostgreSQL/...
│ Authorization: Bearer <token>
└─────────────────────────┘The Service Principal pattern is older than WIF — it’s the Azure-equivalent of “machine user” auth that’s existed since before federated credentials were a standard. Azure does support federated credentials now (documentation); we just haven’t migrated this codepath yet.
Comparison with AWS / GCP
| AWS | GCP | Azure | |
|---|---|---|---|
| Mechanism | Cross-account IAM role + external ID | Workload Identity Federation | Service Principal w/ client secret |
| Long-lived secret at orchestrator? | No | No | Yes (client secret, encrypted at rest) |
| Customer-side primitive | IAM role + trust policy | WIF Pool + Provider + binding | App Registration + role assignment |
Azure is the only of our three clouds where the orchestrator currently holds a long-lived secret (the client secret). It’s encrypted at rest using KMS envelope encryption, and rotated quarterly, but it’s still the weakest link in our cloud trust posture. Migrating Azure to federated credentials is on the roadmap.
See also
- Connect AWS — parallel guide for AWS
- Connect GCP — parallel guide for GCP (the most-secure current connection method)
- Concepts — Security model — why federated credentials matter