Skip to content

Microsoft Azure

This guide explains how RunWhen Local discovers Azure resources and AKS clusters using the native Azure management SDK, and how you can control discovery scope, level-of-detail (LoD), and authentication. Each configuration option is followed by a brief rationale so you can decide whether it is relevant to your environment.

RunWhen Local uses the native azureapi backend by default — first-party azure-mgmt-* SDK directly, which removes the CloudQuery process/binary requirement. The legacy CloudQuery-based backend remains available as a fallback (azureIndexerBackend: cloudquery). Both backends produce the same registry shape, so generation rules and SLX templates do not change when switching.


Choosing a discovery backend

Two backends are available, selected by the top-level azureIndexerBackend key:

azureIndexerBackend: azureapi # default; use "cloudquery" for the legacy path
BackendDescription
azureapi (default)Uses the native Azure management SDK (azure-mgmt-*) directly. Removes the CloudQuery process/binary requirement, integrates better with airgapped images.
cloudquery (legacy)Runs the CloudQuery Azure plugin against the configured subscription(s). Set azureIndexerBackend: cloudquery to opt back in.

When azureIndexerBackend: azureapi is set, the CloudQuery indexer skips Azure (it still runs for AWS / GCP if those are configured) and the native azureapi indexer takes over. When the value is omitted or set to cloudquery, behavior is unchanged from previous releases.

Supported resource types (azureapi backend)

Out of the box the native backend can discover:

  • resource_group (always)
  • virtual_machine
  • azure_storage_accounts
  • azure_network_virtual_networks
  • azure_network_security_groups
  • azure_keyvault_vaults
  • azure_containerservice_managed_clusters

Additional types can be added by registering a collector in src/indexers/azureapi_resource_types.py. If a generation rule references an Azure resource type with no registered collector under the azureapi backend, the build emits a warning and continues; under the legacy cloudquery backend the behavior is unchanged.

Handling Azure API throttling

Azure Resource Manager applies per-subscription read limits and will return HTTP 429. The azureapi indexer automatically retries throttled list calls:

  • Detects throttles by status code (408, 429, 500, 502, 503, 504) and by ARM throttle error codes in the message body.
  • Honors the Retry-After HTTP header when present.
  • Parses the Please try after 'N' seconds hint from the ARM message body when no header is present.
  • Falls back to exponential backoff with full jitter, capped at a per-sleep maximum.
  • Caps the total time spent waiting on any single list call, so a sustained throttle never wedges the build.

The defaults (6 attempts, 2s initial backoff, 60s per-sleep cap, 180s total per call) work well for typical tenants. If you run very large subscriptions or hit sustained throttling, tune these in workspaceInfo.yaml:

azureThrottleMaxAttempts: 10 # default 6
azureThrottleInitialBackoff: 2.0 # default 2.0
azureThrottleMaxBackoff: 120.0 # default 60.0
azureThrottleMaxTotalWait: 600.0 # default 180.0

If you continue to see Failed to list Azure … errors with throttle codes after raising these, narrow the discovery scope by setting per-RG levelOfDetails to none for resource groups that don’t need indexing, or use includeTags / excludeTags, so the indexer makes fewer list calls per run.


AKS Cluster Discovery

Discovery methods

MethodRequired Azure role(s)Typical use-case
KubeconfigStandard Kubernetes RBAC onlyRapid trials when you already possess a kubeconfig. No insight into backing Azure resources.
Service Principal (SP)Azure Kubernetes Service RBAC ReaderProduction-grade installations. Same SP can also cover Azure resource discovery.
Managed Identity (MI)Azure Kubernetes Service RBAC ReaderSecret-less deployments on AKS or VMSS.

Why list each cluster? The Azure ARM API does not expose the Kubernetes API server endpoint. Specifying server: guarantees that the Kubernetes indexer can connect without additional look-ups.

cloudConfig:
kubernetes: null
azure:
aksClusters:
clusters:
- name: aks-cluster-1 # Appears in generated SLX titles
server: https://aks-cluster-1.hcp.eastus.azmk8s.io:443
resource_group: rg-aks-1 # Links namespaces → resource group
subscriptionId: sub-aks-1 # Optional when RG name is unique

Per-cluster Level of Detail

defaultNamespaceLOD and namespaceLODs are honored for AKS exactly as they are for GKE and EKS: a cluster’s defaultNamespaceLOD sets the default LOD for every namespace in that cluster, and namespaceLODs overrides individual namespaces within it. These per-cluster values take precedence over the global defaultLOD for the cluster’s namespaces. Valid values are detailed, basic, and none.

aksClusters:
clusters:
- name: aks-cluster-1
server: https://aks-cluster-1.hcp.eastus.azmk8s.io:443
defaultNamespaceLOD: basic # Applies to all namespaces unless…
namespaceLODs:
kube-system: none

Azure Resource Discovery

Azure discovery inventories cloud resources so that RunWhen Local can match them to automation tasks (CodeBundles). Scoping the crawl lowers API consumption and speeds up runs.

Scoping options

KeyScopeWhen it is useful
subscriptionsList of subscription objectsLimit discovery to specific subscriptions.
resourceGroupLevelOfDetailsPer resource groupApply LoD on an RG basis (e.g. detailed for production RGs).
includeTagsAny ARM resourceDiscover only resources that carry specific tags.
excludeTagsAny ARM resourceOmit resources that carry specific tags.
cloudConfig:
azure:
subscriptions:
- subscriptionId: sub-prod
defaultLOD: basic
resourceGroupLevelOfDetails:
rg-payments: detailed
- subscriptionId: sub-dev
defaultLOD: none # Ignore unless specifically overridden

If the subscriptions: array is absent, CloudQuery will automatically enumerate all subscriptions that the provided credential can access. Under the native azureapi backend, all subscriptions visible to the credential are similarly discovered.


Authentication

Multiple authentication methods are available, including Service Principal and Managed Identity. If multiple authentication methods are available, the order is as follows:

PriorityMethodConfig keyUse-case
1Inline Service PrincipalclientId / clientSecret / tenantId / subscriptionIdQuick start; portable outside Azure.
2Kubernetes secretspSecretNameSP credentials stored as a K8s secret (recommended).
3Managed Identity(none — fallback)Secret-less deployments on AKS or VMSS.

Creating a Service Principal

Terminal window
az account set --subscription <SUB_ID>
az provider register --namespace 'Microsoft.Security'
az ad sp create-for-rbac \
--name runwhen-local-sp \
--scopes /subscriptions/<SUB_ID> \
--role Reader

The output maps into the configuration as follows:

  • appIDclientId
  • passwordclientSecret
  • tenanttenantId

Inline credentials (quick start)

cloudConfig:
azure:
subscriptionId: "<SUB_ID>"
tenantId: "<TENANT_ID>"
clientId: "<APP_ID>"
clientSecret: "<PASSWORD>"
Terminal window
kubectl create secret generic azure-sp \
--from-literal=clientId=<APP_ID> \
--from-literal=clientSecret=<PASSWORD> \
--from-literal=subscriptionId=<SUB_ID> \
--from-literal=tenantId=<TENANT_ID> \
-n runwhen-local
cloudConfig:
azure:
spSecretName: azure-sp

Managed Identity

When neither inline credentials nor spSecretName are provided, the indexer falls back to Managed Identity. Ensure that the identity (either the pod’s user-assigned MI or the node pool’s system-assigned MI) has at least the Reader role on every subscription or resource group you wish to discover.

cloudConfig:
azure:
subscriptionId: "<SUB_ID>"
# No clientId/clientSecret/spSecretName -> Managed Identity

This is the Azure equivalent of AWS EKS IRSA / Pod Identity and GCP GKE Workload Identity. No secrets or long-lived credentials are required — the pod authenticates with the underlying Managed Identity assigned to it (or its node pool).


Cross-cloud Workload Identity parity

CloudIdentity mechanismHow it’s enabledDetection
AWSEKS IRSA / Pod IdentityuseWorkloadIdentity: true or env varsAWS_WEB_IDENTITY_TOKEN_FILE / AWS_CONTAINER_CREDENTIALS_FULL_URI
AzureManaged IdentityFallback (no SP creds set)Absence of clientId / spSecretName
GCPGKE Workload Identity (via ADC)Fallback (no SA key set)Absence of saSecretName / serviceAccountKey / applicationCredentialsFile

All three follow the same shape: explicit credentials first, ambient workload-identity last.


LoD Resolution Order (Quick Reference)

  1. Namespace annotation config.runwhen.com/lod (Kubernetes namespaces within AKS clusters)
  2. Per-cluster defaultNamespaceLOD / namespaceLODs (AKS clusters)
  3. resourceGroupLevelOfDetails inside a subscription object
  4. Workspace-level resourceGroupLevelOfDetails
  5. Subscription-level defaultLOD
  6. Workspace defaultLOD

Configuration Reference

FieldScopeDescription
azureIndexerBackendtop-levelazureapi (default) or cloudquery (legacy/fallback)
azureThrottleMaxAttemptstop-levelMax retry attempts for throttled API calls (default: 6)
azureThrottleInitialBackofftop-levelInitial backoff in seconds (default: 2.0)
azureThrottleMaxBackofftop-levelMax per-sleep backoff in seconds (default: 60.0)
azureThrottleMaxTotalWaittop-levelMax total wait per API call in seconds (default: 180.0)
subscriptionIdcloudConfig.azureThe subscription ID for the specified client/application ID
tenantIdcloudConfig.azureThe tenant ID for the specified client/application ID
clientIdcloudConfig.azureThe client/application ID to use to authenticate
clientSecretcloudConfig.azureThe client/application secret to use to authenticate
spSecretNamecloudConfig.azureName of a Kubernetes secret containing SP auth details
subscriptionscloudConfig.azureList of subscription objects to scope discovery
resourceGroupLevelOfDetailscloudConfig.azureObject/dictionary specifying LoD values for specific resource groups
aksClusterscloudConfig.azureList of AKS clusters to discover using Azure credentials
includeTags / excludeTagscloudConfig.azureOptional tag-based include/exclude filters

End-to-End Example

workspaceName: azure-discovery
workspaceOwnerEmail: platform@example.com
defaultLocation: location-01
defaultLOD: none # Workspace fallback
azureIndexerBackend: azureapi # default; native SDK, no CloudQuery
cloudConfig:
azure:
# Authentication via secret
spSecretName: azure-sp
# Cluster list
aksClusters:
clusters:
- name: aks-demo
server: https://aks-demo.hcp.eastus.azmk8s.io:443
resource_group: rg-aks-demo
subscriptionId: sub-demo
defaultNamespaceLOD: detailed
# Subscription-scoped discovery
subscriptions:
- subscriptionId: sub-demo
defaultLOD: basic
resourceGroupLevelOfDetails:
rg-aks-demo: detailed

Execution flow:

  1. The SP azure-sp authenticates.
  2. The native azureapi indexer is instructed to crawl sub-demo only.
  3. Within that subscription, rg-aks-demo is crawled at detailed LoD; all other RGs default to basic.
  4. The Kubernetes indexer connects solely to aks-demo and indexes every namespace at detailed LoD.

Legacy CloudQuery Backend

If you need the CloudQuery backend (e.g. for resource types not yet covered by the native Azure SDK collectors), set:

azureIndexerBackend: cloudquery

Both backends produce the same registry shape, so generation rules and SLX templates do not change when switching.