Skip to main content

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 fieldDriftWise credential field
appIdClient ID
passwordClient Secret
tenantTenant ID
$AZURE_SUB_IDSubscription 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.

Scope matters

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.

warning

Client secrets expire after 2 years by default. Use OIDC Federation for production to avoid secret rotation.

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

  1. Go to Accounts in the sidebar
  2. Click Add Account and select Microsoft Azure
  3. Enter your Azure Subscription ID (UUID format)
  4. Select your credential type and fill in the fields
  5. 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

PermissionPurpose
Microsoft.ResourceGraph/resources/readExecute Resource Graph queries
Microsoft.Resources/subscriptions/readCredential 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_id and the subscription_id inside credential_ref must 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:

Diagnostic query — internal schema, column names may change
SELECT jsonb_pretty(scan_errors) FROM scan_runs WHERE id = '<scan_id>';

Common errors:

ErrorCauseFix
AuthenticationFailedClient secret is wrong or expiredRegenerate: az ad sp credential reset --name driftwise-scanner
AuthorizationFailedSP exists but has no role on the subscriptionGrant Reader: az role assignment create --assignee <appId> --role Reader --scope /subscriptions/$AZURE_SUB_ID
SubscriptionNotFoundSubscription ID is wrong or SP doesn't have accessVerify the subscription ID and role assignment
InvalidAuthenticationTokenTenant ID is wrongCheck 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:

Diagnostic query — internal schema, column names may change
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.