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| Backend | Description |
|---|---|
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_machineazure_storage_accountsazure_network_virtual_networksazure_network_security_groupsazure_keyvault_vaultsazure_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-AfterHTTP header when present. - Parses the
Please try after 'N' secondshint 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 6azureThrottleInitialBackoff: 2.0 # default 2.0azureThrottleMaxBackoff: 120.0 # default 60.0azureThrottleMaxTotalWait: 600.0 # default 180.0If 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
| Method | Required Azure role(s) | Typical use-case |
|---|---|---|
| Kubeconfig | Standard Kubernetes RBAC only | Rapid trials when you already possess a kubeconfig. No insight into backing Azure resources. |
| Service Principal (SP) | Azure Kubernetes Service RBAC Reader | Production-grade installations. Same SP can also cover Azure resource discovery. |
| Managed Identity (MI) | Azure Kubernetes Service RBAC Reader | Secret-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 uniquePer-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: noneAzure 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
| Key | Scope | When it is useful |
|---|---|---|
subscriptions | List of subscription objects | Limit discovery to specific subscriptions. |
resourceGroupLevelOfDetails | Per resource group | Apply LoD on an RG basis (e.g. detailed for production RGs). |
includeTags | Any ARM resource | Discover only resources that carry specific tags. |
excludeTags | Any ARM resource | Omit 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 overriddenIf the
subscriptions:array is absent, CloudQuery will automatically enumerate all subscriptions that the provided credential can access. Under the nativeazureapibackend, 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:
| Priority | Method | Config key | Use-case |
|---|---|---|---|
| 1 | Inline Service Principal | clientId / clientSecret / tenantId / subscriptionId | Quick start; portable outside Azure. |
| 2 | Kubernetes secret | spSecretName | SP credentials stored as a K8s secret (recommended). |
| 3 | Managed Identity | (none — fallback) | Secret-less deployments on AKS or VMSS. |
Creating a Service Principal
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 ReaderThe output maps into the configuration as follows:
appID→clientIdpassword→clientSecrettenant→tenantId
Inline credentials (quick start)
cloudConfig: azure: subscriptionId: "<SUB_ID>" tenantId: "<TENANT_ID>" clientId: "<APP_ID>" clientSecret: "<PASSWORD>"Storing credentials in a secret (recommended)
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-localcloudConfig: azure: spSecretName: azure-spManaged 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 IdentityThis 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
| Cloud | Identity mechanism | How it’s enabled | Detection |
|---|---|---|---|
| AWS | EKS IRSA / Pod Identity | useWorkloadIdentity: true or env vars | AWS_WEB_IDENTITY_TOKEN_FILE / AWS_CONTAINER_CREDENTIALS_FULL_URI |
| Azure | Managed Identity | Fallback (no SP creds set) | Absence of clientId / spSecretName |
| GCP | GKE 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)
- Namespace annotation
config.runwhen.com/lod(Kubernetes namespaces within AKS clusters) - Per-cluster
defaultNamespaceLOD/namespaceLODs(AKS clusters) resourceGroupLevelOfDetailsinside a subscription object- Workspace-level
resourceGroupLevelOfDetails - Subscription-level
defaultLOD - Workspace
defaultLOD
Configuration Reference
| Field | Scope | Description |
|---|---|---|
azureIndexerBackend | top-level | azureapi (default) or cloudquery (legacy/fallback) |
azureThrottleMaxAttempts | top-level | Max retry attempts for throttled API calls (default: 6) |
azureThrottleInitialBackoff | top-level | Initial backoff in seconds (default: 2.0) |
azureThrottleMaxBackoff | top-level | Max per-sleep backoff in seconds (default: 60.0) |
azureThrottleMaxTotalWait | top-level | Max total wait per API call in seconds (default: 180.0) |
subscriptionId | cloudConfig.azure | The subscription ID for the specified client/application ID |
tenantId | cloudConfig.azure | The tenant ID for the specified client/application ID |
clientId | cloudConfig.azure | The client/application ID to use to authenticate |
clientSecret | cloudConfig.azure | The client/application secret to use to authenticate |
spSecretName | cloudConfig.azure | Name of a Kubernetes secret containing SP auth details |
subscriptions | cloudConfig.azure | List of subscription objects to scope discovery |
resourceGroupLevelOfDetails | cloudConfig.azure | Object/dictionary specifying LoD values for specific resource groups |
aksClusters | cloudConfig.azure | List of AKS clusters to discover using Azure credentials |
includeTags / excludeTags | cloudConfig.azure | Optional tag-based include/exclude filters |
End-to-End Example
workspaceName: azure-discoveryworkspaceOwnerEmail: platform@example.comdefaultLocation: location-01defaultLOD: 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: detailedExecution flow:
- The SP azure-sp authenticates.
- The native
azureapiindexer is instructed to crawl sub-demo only. - Within that subscription, rg-aks-demo is crawled at detailed LoD; all other RGs default to basic.
- The Kubernetes indexer connects solely to
aks-demoand 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- Currently supported source plugin: Azure
- Available resources: See the CloudQuery tables documentation
Both backends produce the same registry shape, so generation rules and SLX templates do not change when switching.