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 1 year by default. Use OIDC Federation for production to avoid secret rotation.
Option B: OIDC Federation (recommended for production)
Open Cloud Scan → + Add Account → Azure in DriftWise. The "Setup
Instructions" panel renders the exact federated credential JSON with
your org UUID already filled in (subject: org-<YOUR-ORG-UUID>) — copy
from there instead of editing this template by hand. The block below is
reference; the UI is canonical.
Configure the service principal to trust DriftWise's per-org federated identity instead of using a static secret:
# Replace <YOUR-ORG-UUID> with the value shown in the UI's Setup Instructions.
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://federation.driftwise.ai",
"subject": "org-<YOUR-ORG-UUID>",
"audiences": ["https://app.driftwise.ai/federation"]
}'
The subject is per-DriftWise-org. If you connect a second DriftWise org,
create a second federated credential with a different name and the
new org's UUID. The issuer and audience are deployment-level and shared
across orgs.
3. Add the account in DriftWise
Via the UI
If you didn't save the output from step 1, retrieve the values you need:
# Tenant ID
az account show --query tenantId -o tsv
# Client ID (appId of the service principal)
az ad sp list --display-name driftwise-scanner --query '[0].appId' -o tsv
# Subscription ID
az account show --query id -o tsv
Then:
- Open Cloud Scan in the left sidebar.
- Click + Add Account and select Microsoft Azure.
- Fill in the form:
- Display Name — any label you'll recognize (e.g.
Production Azure). - Subscription ID — the UUID of the subscription to scan.
- Default Region — optional; used as default when starting scans.
- Credential Type —
OIDC Federation(recommended) orClient Secret. - Both credential types:
- Tenant ID — paste the
az account show --query tenantIdoutput. - Client ID — paste the
az ad sp listoutput (the SP'sappId).
- Tenant ID — paste the
- Client Secret only:
- Client Secret — the
passwordfromaz ad sp create-for-rbacoutput. Not recoverable after creation — regenerate withaz ad sp credential resetif lost.
- Client Secret — the
- Display Name — any label you'll recognize (e.g.
- Click Register Account. The backend validates against Azure before saving — a bad Tenant ID or Client ID fails here, not at first scan.
Via the API
Call POST /orgs/:id/accounts with provider: "azure" and one of two
credential types:
credential_type: "azure_sp"—credential_refis a JSON-encoded object withclient_id,client_secret, andtenant_id.credential_type: "azure_oidc"—credential_refis a JSON-encoded object withclient_id,tenant_id, and_credential_type: "azure_oidc"(see note below; no static secret).
_credential_type field inside credential_refToday the server parses credential_ref independently of the top-level
credential_type field, so OIDC credentials must also include
_credential_type inside the inner JSON to trigger the federated code path.
Without it, the request fails with client_secret is required because the
parser falls through to the service-principal branch. This duplication will
be removed once the backend merges the fields at request time.
The subscription ID comes from external_account_id on the enclosing
account — it is not a field inside credential_ref. Credentials are
validated against Azure before save, and the provider-reported
subscription must match external_account_id.
See the accounts tag of the API
reference for the full
request and response shapes.
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.
Reading Terraform state from Azure Blob
When you link an Azure cloud account to an
Azure Blob state source, DriftWise uses the
linked account's service principal (azure_sp) or OIDC-federated
identity (azure_oidc) to read the blob. The Reader role above
does NOT include blob data reads — ARM Reader and blob data-plane
reads are different RBAC scopes.
Required role:
- Storage Blob Data Reader on the storage account (or scoped to the specific container)
Grant at storage-account scope — DriftWise reads one blob per refresh and does not enumerate containers. The grant is read-only; DriftWise never writes, deletes, or acquires a lease on the blob.
DriftWise authenticates via Entra ID (AAD) tokens only. Storage account keys, shared-access signatures, and connection strings are not supported — use the RBAC model above instead.
If you prefer strict separation, register a second cloud account whose service principal has only the blob-read role, then link that account to the state source instead of the scanner account.
Sovereign Azure clouds (.blob.core.chinacloudapi.cn,
.blob.core.usgovcloudapi.net, etc.) are not supported by the
current driver — it constructs service URLs against
blob.core.windows.net only. Reach out if you need sovereign
cloud support.
Troubleshooting
Scan completes with 0 resources and 0 errors
Azure Resource Graph returns an empty result set — not an error — when the subscription has no resources the principal can see. The scan completes successfully and looks identical to a credentials-working-but-misconfigured state.
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 — the account's
external_account_idis the subscription the scan targets. If you set this to the wrong subscription ID, the scan queries an empty or different scope. - Multiple subscriptions — verify you're pointing at the right one:
az account list --output table - Reader scoped too narrowly — if the SP's Reader role is on a resource group rather than the subscription, Resource Graph will only return resources inside that RG.
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: 1 year). 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.