Azure Cloud Account
Connect an Azure subscription so DriftWise can scan live infrastructure and detect drift against your Terraform state. DriftWise discovers resources via Azure Resource Graph, which projects ARM data across every service the principal can see — there is no per-service enumeration. See Cloud Discovery for the full model.
Prerequisites
- Azure CLI installed and authenticated (
az login) - An Azure subscription with permission to create App Registrations
- A DriftWise API key or OIDC login
1. Create a Service Principal
DriftWise authenticates as an Azure service principal with read-only access.
export AZURE_SUB_ID=$(az account show --query id -o tsv)
az ad sp create-for-rbac \
--name "driftwise-scanner" \
--role "Reader" \
--scopes "/subscriptions/$AZURE_SUB_ID" \
--output json
Save the output — you'll need these values:
| SP output field | DriftWise credential field |
|---|---|
appId | Client ID |
password | Client Secret |
tenant | Tenant ID |
$AZURE_SUB_ID | Subscription ID |
The Reader role at subscription scope covers everything DriftWise needs. Resource Graph queries use Microsoft.ResourceGraph/resources/read, which Reader grants, and Resource Graph projects ARM data for every resource type the principal can see. No additional roles are required.
Grant Reader at the subscription (or management group) level. A resource-group-scoped Reader will only return resources inside that group — Resource Graph respects the caller's effective permissions.
2. Choose a Credential Type
Option A: Client Secret (simple, for dev/test)
Use the service principal credentials from step 1 directly. No additional setup needed.
Client secrets expire after 2 years by default. Use OIDC Federation for production to avoid secret rotation.
Option B: OIDC Federation (recommended for production)
Configure the service principal to trust DriftWise's OIDC identity instead of using a static secret:
APP_ID=$(az ad sp list --display-name driftwise-scanner --query '[0].appId' -o tsv)
az ad app federated-credential create \
--id $APP_ID \
--parameters '{
"name": "driftwise-federation",
"issuer": "https://driftwise.ai",
"subject": "system:serviceaccount:driftwise:scan-worker",
"audiences": ["https://driftwise.io/federation"]
}'
3. Add the Account in DriftWise
Via the UI
- Go to Accounts in the sidebar
- Click Add Account and select Microsoft Azure
- Enter your Azure Subscription ID (UUID format)
- Select your credential type and fill in the fields
- Click Save
Via the API
Client Secret:
curl -X POST "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/accounts" \
-H "x-api-key: $DRIFTWISE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Production Azure",
"provider": "azure",
"external_account_id": "'$AZURE_SUB_ID'",
"credential_type": "azure_sp",
"credential_ref": "{\"client_id\": \"<appId>\", \"client_secret\": \"<password>\", \"tenant_id\": \"<tenant>\", \"subscription_id\": \"'$AZURE_SUB_ID'\"}"
}'
OIDC Federation:
curl -X POST "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/accounts" \
-H "x-api-key: $DRIFTWISE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Production Azure",
"provider": "azure",
"external_account_id": "'$AZURE_SUB_ID'",
"credential_type": "azure_oidc",
"credential_ref": "{\"client_id\": \"<appId>\", \"tenant_id\": \"<tenant>\", \"subscription_id\": \"'$AZURE_SUB_ID'\"}"
}'
Supported Resources
DriftWise discovers every resource Azure Resource Graph returns for the subscription — there is no per-type allowlist. Resources are normalized into eight broad categories (compute, storage, database, network, iam, serverless, messaging, other) regardless of provider. See Cloud Discovery for the full list with examples.
The underlying ARM resource type (for example Microsoft.Compute/virtualMachines) is preserved alongside the category for exact-match drift detection.
Because Resource Graph returns full ARM properties in one call, Azure scans have no separate enrichment phase — every row is stored with enrichment_status = n/a.
Required Permissions
| Permission | Purpose |
|---|---|
Microsoft.ResourceGraph/resources/read | Execute Resource Graph queries |
Microsoft.Resources/subscriptions/read | Credential validation |
Both are included in the built-in Reader role. Resource Graph queries reflect whatever ARM data the principal is authorized to see, so granting Reader at subscription scope transparently enables discovery of every service type in the subscription.
Troubleshooting
Scan completes with 0 resources and 0 errors
This is the most confusing Azure failure mode — credentials are valid but nothing is found. Unlike GCP (which returns 403), Azure's List APIs return empty arrays when no resources exist.
Check if the subscription actually has resources:
az resource list --subscription $AZURE_SUB_ID --output table
If this returns [], the subscription is empty — the scan is working correctly.
Other causes:
- Wrong subscription ID — the
external_account_idand thesubscription_idinsidecredential_refmust match. If they differ, the scan may query the wrong subscription. - Resources in a different subscription — if you have multiple subscriptions, verify you're pointing at the right one:
az account list --output table
Scan completes with errors
Check the scan errors:
SELECT jsonb_pretty(scan_errors) FROM scan_runs WHERE id = '<scan_id>';
Common errors:
| Error | Cause | Fix |
|---|---|---|
AuthenticationFailed | Client secret is wrong or expired | Regenerate: az ad sp credential reset --name driftwise-scanner |
AuthorizationFailed | SP exists but has no role on the subscription | Grant Reader: az role assignment create --assignee <appId> --role Reader --scope /subscriptions/$AZURE_SUB_ID |
SubscriptionNotFound | Subscription ID is wrong or SP doesn't have access | Verify the subscription ID and role assignment |
InvalidAuthenticationToken | Tenant ID is wrong | Check az account show --query tenantId |
Client secret expired
Azure SP secrets expire (default: 2 years). Regenerate:
az ad sp credential reset --name driftwise-scanner --output json
Update the client_secret in DriftWise with the new password value. Consider switching to OIDC Federation to avoid this entirely.
Scan stuck in "pending"
The scan worker hasn't claimed it. Check the worker logs:
kubectl logs -f deploy/scan-worker
If the worker is running but scans stay pending, check for stuck scans:
SELECT id, status, retry_count, started_at FROM scan_runs
WHERE status = 'running' AND started_at < NOW() - INTERVAL '10 minutes';
Some resources missing after creation
Azure Resource Graph has a propagation delay of up to several minutes after a resource is created, deleted, or modified. Re-run the scan, or let the next scheduled scan pick up the changes.