Azure CLI Commands Cheat Sheet
#You Inherited a Subscription. Here Is How to Read It From a Terminal.#link
If you have just been handed the security channel for an Azure subscription you did not build, the portal is the worst place to start. It shows you the resources that exist, not the permissions that govern them, and it hides the one distinction that causes more confusion than anything else in Azure: management plane and data plane are authorized separately, by different systems, often with different role assignments. The CLI is where that separation becomes visible instead of mysterious. These are the commands I would want you to have in the first week, in roughly the order you will need them.
- ▪Almost every confusing Azure 403 is a plane problem, not a permission typo. Confirm which plane you are hitting before editing a single role assignment.
- ▪`az storage` defaults to shared-key authentication via the management plane. Without `--auth-mode login`, data-plane RBAC is never evaluated, which is why your role assignment appears to do nothing.
- ▪Entra ID directory roles and Azure RBAC roles are two unrelated systems. `az role assignment list` cannot see directory roles, and PIM and Conditional Access require `az rest` against Microsoft Graph.
- ▪The activity log retains 90 days by default. If nobody created a diagnostic setting, the evidence you need next quarter will not exist.
- ▪The CLI caches live tokens on disk. On a shared jump host, that is a credential store, and it should be treated as one.
Before You Run a Single Command
The most common Azure CLI mistake is not a bad command, it is a correct command aimed at the wrong subscription. Every `az` invocation resolves to a current subscription unless you override it, and an empty result from a correctly written query in the wrong subscription looks exactly like a correctly written query against an empty tenant. Establish context first, and establish it explicitly.
az loginaz account list --output tableaz account set --subscription 11112222-3333-4444-5555-666677778888az account show --query "{Name:name, Tenant:tenantId, User:user.name}"
Pin defaults per working directory with `az config set defaults.group=rg-prod defaults.location=westeurope`, and pass `--subscription` on any command in a script. Relying on ambient state is how a read-only audit turns into a write against production. Note that `az configure --defaults` still works but the newer `az config set` is what the CLI documentation now leads with.
The Split That Explains Most Confusing Errors
Azure authorizes control-plane operations through Azure Resource Manager and data-plane operations through each service's own endpoint, and the two do not share a permission check. An ARM role assignment that grants `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read` is a control-plane action. It does not, by itself, let you read a blob. The data-plane read is a separate authorization decision against a separate role such as Storage Blob Data Reader, made by the storage endpoint, not by ARM. Engineers who learned IAM in AWS tend to expect one evaluation chain; Azure has two, and the CLI will happily use neither if you let it default.
That diagram is why a scoped role assignment can look broken. You granted Storage Blob Data Reader at the container scope, `az storage blob list` still returns a 403, and the reason is that the CLI never presented a token for the storage endpoint to evaluate. Add `--auth-mode login` and the same command succeeds. This is the single highest-yield thing to internalize about the Azure CLI.
| Operation | Plane | Authorization system | Role type that grants it |
|---|---|---|---|
| az storage account list | Management | ARM | Azure RBAC, e.g. Reader or Storage Account Contributor |
| az storage blob list --auth-mode login | Data | Storage endpoint | Storage Blob Data Reader or higher |
| az keyvault secret show | Data | Key Vault endpoint | Key Vault Secrets User in the vault access policy or RBAC |
| az vm list | Management | ARM | Azure RBAC, Virtual Machine Reader |
| az monitor activity-log list | Management | ARM | Azure RBAC, Monitoring Reader |
Disabling shared-key access on a storage account with `--allow-shared-key-access false` is a real hardening step, but it will break every script, pipeline, and third-party tool that authenticates with an account key. Inventory consumers of the account before you flip it, not after. The same warning applies to setting `--default-action Deny` on a storage firewall.
Public Blob Access, and Why It Keeps Happening
In late 2022, SOCRadar reported what became known as BlueBleed: roughly 65,000 organizations with data exposed through publicly reachable Azure Blob Storage containers, surfaced by scanning for the default `blob.core.windows.net` endpoint rather than exploiting any vulnerability. Nothing was broken. The containers were simply configured to allow anonymous reads, frequently by an application team that needed to serve a static asset and never revisited the setting. Azure has since changed the default so that new storage accounts disallow public blob access, but accounts created before that change kept whatever they had, and `az` is the fastest way to find them across an estate.
az storage account list \--query "[?allowBlobPublicAccess].{Name:name, ResourceGroup:resourceGroup, Id:id}" \--output table
az storage account update \--name prodlogs \--resource-group rg-prod \--allow-blob-public-access false
Setting the account-level flag to false does not delete a container access policy that was already set to `blob`. The account flag is a ceiling; the container has to be individually checked with `az storage container show-permission --name assets --account-name prodlogs --auth-mode login`. Remediating one without the other leaves the exposure live and gives you a false sense of closure.
Directory Roles Are Not Azure RBAC Roles
There are two role systems in an Azure tenant and the CLI only speaks one of them natively. Azure RBAC roles such as Contributor, Reader, and Key Vault Administrator live in ARM, are assigned to a scope like a subscription or resource group, and appear in `az role assignment list`. Entra ID directory roles such as Global Administrator, Privileged Role Administrator, and Application Administrator live in Microsoft Graph, are assigned tenant-wide, and are invisible to every `az role assignment` command. An auditor who runs only the ARM query will report a tenant with no privileged accounts, which is a very expensive way to be wrong.
az role assignment list \--assignee ci-deploy@contoso.example \--all \--query "[].{Role:roleDefinitionName, Scope:scope}" \--output table
az rest --method GET \--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \--query "value[].{Name:displayName, State:state}" \--output table
The `--all` flag on `az role assignment list` includes assignments inherited from management groups and subscriptions, which the default scope-limited call omits. Audit findings that miss inherited management-group assignments are common and entirely avoidable. Also note that a newly created role assignment can take several minutes to become effective; a 403 immediately after granting a role is not proof the grant failed.
Reading a Denial Instead of Retrying It
Azure returns a structured error body for ARM calls, and the CLI discards most of it. Adding `--debug` to a failing command surfaces the full response, which usually names the authorization mechanism that rejected you. `ForbiddenByRbac`, `ForbiddenByPolicy`, and `ForbiddenBySecurityGroup` are three genuinely different problems with three different owners, and the one-line CLI error collapses all of them into the word Forbidden.
ARM response for a Key Vault secret read denied by RBAC
The CLI prints only the message. The error code and the api-version are what tell you which layer rejected the request and which role model the vault is using.
{
"error": {
"code": "ForbiddenByRbac",
"message": "The operation is not allowed by RBAC. If role assignments were recently changed, please wait several minutes for role assignments to become effective."
}
}The code matters more than the message. `ForbiddenByPolicy` means an Azure Policy assignment denied the request and no amount of role assignment will fix it. `ForbiddenByRbac` means you are in the right plane and the wrong role. A `Forbidden` with no code at all is often a network control rather than an authorization one, which is a category most people forget to check. Read the code before you read the message.
The Activity Log Has a 90-Day Memory
Azure's control-plane audit record is the activity log, and by default it is retained for 90 days at the subscription level with no export. That is the entire forensic history you have unless somebody created a diagnostic setting shipping Administrative, Security, and Policy categories to a Log Analytics workspace or storage account. In practice this means an incident discovered in month four has no control-plane evidence at all, and no amount of CLI skill recovers it.
On Windows, quoting for `az` is a genuine trap and it is not the same in cmd.exe and PowerShell. A JSON argument written as `--logs "[{\"category\":\"Administrative\",\"enabled\":true}]"` works in bash but is mangled by cmd.exe, and PowerShell interprets the braces differently again. The reliable form on Windows is to write the JSON to a file and pass `--logs @arguments.json`, or to wrap the whole argument in single quotes under PowerShell. Expect at least one silent misconfiguration from this before you standardize on the file form.
az monitor diagnostic-settings create \--name export-admin-log \--resource /subscriptions/11112222-3333-4444-5555-666677778888/resourceGroups/rg-prod/providers/Microsoft.Storage/storageAccounts/prodlogs \--workspace /subscriptions/11112222-3333-4444-5555-666677778888/resourceGroups/rg-mon/providers/Microsoft.OperationalInsights/workspaces/law-security \--logs '[{"category":"Administrative","enabled":true},{"category":"Security","enabled":true},{"category":"Policy","enabled":true}]'
CLI vs PowerShell vs Resource Graph
Three tools answer Azure questions and they are not interchangeable. The Azure CLI is the fastest path to a one-off answer and the easiest to run in a Linux pipeline. Az PowerShell is better when you need to iterate over hundreds of objects with real control flow and error handling, because piping objects beats re-parsing JSON in bash. Azure Resource Graph is the only one of the three that queries the whole tenant in a single call, and for estate-wide questions it is not merely faster, it is the difference between a query that returns in seconds and a loop that runs for an hour.
| Tool | Best at | Weak at | Reach for it when |
|---|---|---|---|
| Azure CLI | One-off commands, CI scripts, Linux runners | Complex loops and retries | You need one answer now |
| Az PowerShell | Object pipelines, try/catch, bulk remediation | Cold start cost, cross-platform setup | You are changing many resources safely |
| Resource Graph | Tenant-wide inventory and cross-subscription queries | Write operations, very recent changes | You need to know what exists anywhere |
| az rest | Anything the CLI has no first-class command for | Ergonomics, discoverability | The API exists but the command does not |
The Resource Graph extension is the one people skip and should not. `az extension add --name resource-graph` unlocks tenant-scale queries, and the syntax is worth ten minutes of learning because it replaces most of the loops you would otherwise write in bash.
az extension add --name resource-graphaz graph query -q "Resources| where type =~ 'microsoft.storage/storageaccounts'| where properties.allowBlobPublicAccess == true| project name, resourceGroup, subscriptionId" --output table
Hardening Order for a Subscription You Inherited
- Enumerate every subscription and confirm which tenant you are actually in with `az account list --output table`.
- Dump ARM role assignments at subscription and management-group scope, including inherited ones, and look for Contributor or Owner granted to service principals.
- Check Microsoft Graph separately for directory roles; the ARM audit will not surface Global Administrator or Privileged Role Administrator.
- Query storage accounts for public blob access and remediate at both the account flag and the container access policy.
- Check every Key Vault for soft delete, purge protection, and public network access in one query rather than vault by vault.
- Create a diagnostic setting exporting Administrative, Security, and Policy logs before anything else, because the clock on the 90-day window is already running.
- Disable shared-key access on storage accounts only after you have inventoried the consumers that will break.
The Azure CLI writes live access and refresh tokens into a cache under `~/.azure` on the machine where you ran `az login`. On a shared jump host that directory is a credential store with the same privileges you have, and it is not covered by your endpoint agent's usual detection logic. `az account get-access-token --resource https://storage.azure.com/` will hand you a working bearer token, which is precisely why anyone else on that box can do the same. Use a dedicated host or a managed identity, and clear the cache on logout.
Azure rewards the engineer who asks which plane, which system, and which caller before touching a role assignment. Almost every 403 you will meet in your first month resolves once you can answer those three questions in order, and the CLI is the only tool that makes all three answers visible in one session.