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.

Azure federated credentials (workload identity via the orchestrator’s OIDC issuer, no client secret) is on the roadmap and matches the GCP WIF setup. Until that ships, Service Principal with client secret is the supported pattern. The client secret is encrypted at rest in the orchestrator using KMS envelope encryption.

What this does

After setup, the orchestrator can deploy resources into your Azure subscription by:

  1. Authenticating to Azure AD using your Service Principal’s client ID + client secret (the standard client credentials grant)
  2. Receiving a short-lived bearer token scoped to Azure Resource Manager
  3. 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)
  • az CLI 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.

The client secret is a long-lived bearer credential. The orchestrator stores it encrypted at rest using KMS envelope encryption. We rotate quarterly per spec security model. When Azure federated credentials ship, this whole step goes away — you’ll trust the orchestrator’s OIDC issuer instead.

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:

FieldValue
Account labelFree-text, e.g. prod-azure
Tenant IDFrom Step 2 output (abcdef00-...)
Subscription IDThe Azure subscription ID (SUBSCRIPTION_ID above)
Client IDApp ID from Step 1
Client SecretThe password from Step 2 (one-time-visible value)
Default regionAzure region (e.g., eastus, westeurope)

Click Connect. The orchestrator runs the probe:

  1. Calls Azure AD with client credentials → receives a token
  2. Calls Azure ARM subscriptions/{id} API as the smoke test
  3. 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 table

Should 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., Contributor doesn’t include Microsoft.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"
done

These 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

AWSGCPAzure
MechanismCross-account IAM role + external IDWorkload Identity FederationService Principal w/ client secret
Long-lived secret at orchestrator?NoNoYes (client secret, encrypted at rest)
Customer-side primitiveIAM role + trust policyWIF Pool + Provider + bindingApp 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

Was this page helpful?