OnCallReady

Lesson 22.1 · Azure I: CLI, Identity & Data Planes · 21 min read

What `az` actually talks to

In plain words

Imagine a huge block of flats. Each flat has a full address: city, street, building, flat number. The city hall keeps a register of who owns what, and you go there to build a new flat or change one. But to get into a flat's safe, you deal with the safe itself, not with city hall.

Azure is that city. A subscription is a street, a resource group a building, and a resource a flat, and every resource has an ID that spells out the whole address: /subscriptions/.../resourceGroups/rg-shop-prod/providers/Microsoft.KeyVault/vaults/kv-shop-prod. az mostly talks to city hall, Azure Resource Manager at management.azure.com, the control plane. Some services, like Key Vault and Storage, also have their own door, the data plane, with its own checks.

Your company runs its platform on Azure, Microsoft's cloud: a huge set of data centres where you rent machines, networks, databases and clusters by the hour instead of buying hardware. Someone asks "which Kubernetes clusters do we run in prod, and who can change them?" Clicking through a web page for that takes an hour and proves nothing. One command answers it in a second. This chapter teaches that command line, and the identity and permission model behind every "Forbidden" you will ever get from Azure.

What you need to know already: 1.1 (a shell and its prompt), 7.11 (JSON and jq), 12.1 (Terraform describes infrastructure as code; its azurerm provider talks to Azure), 15.1 (a Kubernetes cluster runs pods on nodes).

Words first

The rule for this block

Nothing from the portal. Everything in Terraform. The portal is for verification only.

Why? A click in the portal leaves no record of why and cannot be repeated exactly. Terraform (Ch 12-14) keeps the whole setup in git, reviewed and repeatable.

So why learn az at all? Because the CLI is how you look at Azure quickly and precisely, how you script the checks a pipeline runs, and how you do break-glass work (an emergency fix by hand, when the normal path is broken) at 3am. Every resource you create with az in these missions has a Terraform twin (azurerm_*), and chapter 12's engine already describes the same subscription with the same IDs.

The hierarchy

Azure organises everything in nested boxes:

Entra ID tenant            11111111-2222-3333-4444-555555555555   (identities live here)
 └─ management group       optional, for policy and RBAC across subscriptions
     └─ subscription       00000000-1111-2222-3333-444444444444   (billing + RBAC boundary)
         └─ resource group rg-oncall-lab                           (lifecycle container)
             └─ resource   vnet-sysop, aks-sysop, kv-shop-prod ...

The long numbers are GUIDs (globally unique IDs): 32 hex digits in five groups. Azure gives every tenant, subscription and identity one.

RBAC (role-based access control) is Azure's permission system: who gets which role on which box. It is lesson 22.13; for now, just know that permissions are granted on one of these boxes and flow down.

Resource IDs - learn to read them

Every resource has one ID, a path through the boxes:

/subscriptions/00000000-1111-2222-3333-444444444444
  /resourceGroups/rg-shop-prod
    /providers/Microsoft.KeyVault
      /vaults/kv-shop-prod

Read it as: subscription, resource group, then the provider (the Azure service that owns this kind of resource, here Microsoft.KeyVault), the resource type (vaults) and the name.

/subscriptions/<id>/resourceGroups/<rg>/providers/<Namespace>/<type>/<name>, and child resources keep going: .../virtualNetworks/vnet-sysop/subnets/snet-aks. IDs are case-insensitive - you will see resourceGroups and resourcegroups in different outputs for the same thing, and Key Vault errors lowercase the whole path. So never compare them with a case-sensitive grep; use grep -i.

Control plane and data plane

az talks to Azure Resource Manager (ARM, at https://management.azure.com) for almost everything: create a vault, list subnets, scale a node pool. ARM is the front door for managing resources. That is the control plane.

Some services also have a data plane: their own address where you use the contents of the resource, with its own permission check:

control plane   management.azure.com          create / configure the Key Vault
data plane      kv-shop-prod.vault.azure.net  read and write the secrets in it
data plane      stsysoplab.blob.core.windows.net   read and write files (blobs)
data plane      <cluster>.hcp.westeurope.azmk8s.io  the Kubernetes API itself

An analogy: the control plane is the building's management office (build a safe, move it, give out keys); the data plane is opening the safe. Being the building's owner does not mean you know the combination. This split is the root of half the permission surprises in this chapter (lesson 22.23).

Signing in

Before az can do anything it needs a token: a short-lived signed string from Entra ID that says "this is learner, valid for the next hour". az login gets one.

az login                        # opens a browser; on a server it cannot
az login --use-device-code      # prints a code, you enter it on another device
az login --service-principal -u <appId> -p <secret> --tenant <tenant>
az login --identity             # only on Azure machines with a managed identity

On a headless box (no screen, no browser) like oncall-lab, the device code flow is the one you use:

# the next mission installs azure-cli and signs you in
az login --use-device-code
To sign in, use a web browser to open the page https://microsoft.com/devicelogin
and enter the code F7K2QXP9R to authenticate.

Recent CLI versions then show a tenant and subscription picker instead of dumping every subscription as JSON. Enter keeps the default.

Pipelines should not log in with a password at all. They use federated credentials (a trust rule: "tokens from this CI system for this repo count as this identity"), so no secret is stored anywhere. Lesson 22.21 builds one.

Where the credentials go

~/.azure/azureProfile.json      the subscriptions you can see
~/.azure/msal_token_cache.json  access AND refresh tokens
~/.azure/config                 az config settings

An access token works for about an hour. A refresh token is used to get new access tokens without logging in again, for days. So the token cache is a credential: anyone who can read it is you until the refresh token expires. Never bake ~/.azure into a container image or copy it to a shared box.

Subscriptions

az account commands are about where you are working:

az account show                                   # where am I?
az account list -o table                          # what can I see?
az account set --subscription sub-oncall-lab       # switch (name or id)
az group list --subscription sub-platform-prod    # one command, other sub
# once signed in
az account list -o table
Name               CloudName   SubscriptionId                        TenantId                              State    IsDefault
-----------------  ----------  ------------------------------------  ------------------------------------  -------  ---------
sub-oncall-lab   AzureCloud  00000000-1111-2222-3333-444444444444  11111111-2222-3333-4444-555555555555  Enabled  True
sub-platform-prod  AzureCloud  00000000-1111-2222-3333-555555555555  11111111-2222-3333-4444-555555555555  Enabled  False

Two subscriptions, same tenant. IsDefault True marks the current one - every command without --subscription goes there.

The most expensive CLI mistake is running a destructive command against the subscription you thought you had selected. az account show before anything that writes, or pass --subscription explicitly in scripts.

Errors look like this

ERROR: (AuthorizationFailed) The client '[email protected]' with object id
'99999999-8888-7777-6666-555555555555' does not have authorization to perform action
'Microsoft.Resources/subscriptions/resourcegroups/write' over scope '...' or the scope is
invalid. If access was recently granted, please refresh your credentials.
Code: AuthorizationFailed

Read it in order: (Code) first - that is what you search for. Then who (the client and its object id, the GUID of your identity in Entra ID), the action it tried (.../resourcegroups/write = create or change a resource group) and the scope (which box).

Exit status is 1 for a service error and 2 for a CLI argument error (missing -g, bad -o value) - scripts can tell them apart.

Defaults

az config set defaults.group=rg-oncall-lab   # -g becomes optional
az config set core.output=table             # default output format
az config unset defaults.group

-g (long form --resource-group) is the flag almost every az command uses to say which resource group. az config set stores a default for it in ~/.azure/config; unset removes it.

Convenient, and a trap: a default group silently applies to every command that accepts --resource-group, including az role assignment list, which then lists only that group's permissions. Use defaults interactively, never in scripts.

In this lab the tenant and its subscriptions are simulated (simulator); the commands, output shapes and error codes are the real CLI's.

What you can now do

Why it helps

The first things you'll do in any Azure incident are "where am I?" and "what exactly is this?": az account show to confirm the subscription, then reading resource IDs and error codes. The most expensive CLI mistake is a destructive command in the subscription you thought you'd switched away from, and knowing to check or pass --subscription explicitly prevents it.

Reading errors precisely also saves time: (AuthorizationFailed) names the exact action and scope you're missing, and exit code 1 versus 2 tells a script whether Azure refused or the command was wrong. Knowing where credentials live, ~/.azure/msal_token_cache.json, tells you why a build image or shared jump box must never contain it. And headless sign-in with --use-device-code is how you'll log in on servers like oncall-lab.

FAQ

Why does az login not work on a server?

Plain az login opens a browser for interactive sign-in, and a headless server like oncall-lab has none. Use az login --use-device-code: it prints a URL and a code, and you complete the sign-in on another device such as your laptop. On Azure compute with a managed identity, use az login --identity. Pipelines should use federated credentials through their platform's login action, not passwords.

Are resource IDs case-sensitive?

No, they're case-insensitive, and different outputs show the same ID with different casing: resourceGroups in one place, resourcegroups in another, and Key Vault errors lowercase the whole path. So never compare IDs with a case-sensitive grep or == in a script, which fails to match things that are equal. Use a case-insensitive comparison: in bash lowercase both sides first.

What is in ~/.azure and why does it matter?

azureProfile.json lists the subscriptions you can see, config holds your az config settings, and msal_token_cache.json holds your access and refresh tokens. The token cache is a credential: anyone who can read it can act as you until the refresh token expires. Never bake ~/.azure into a container image, copy it to a shared machine, or commit it. az logout clears it.

Why are defaults like defaults.group risky?

Because they silently apply to every command that accepts the parameter. With defaults.group set, az role assignment list lists only that group's scope and you may conclude someone has no access. A script written on your machine behaves differently on a colleague's. Use defaults for interactive convenience if you like, but never in scripts, and pass --subscription and -g explicitly for anything that writes.

What do az's exit codes mean?

0 is success, 1 means the service returned an error, such as AuthorizationFailed or ResourceNotFound, and 2 means the CLI rejected the command itself, like a missing required argument or an invalid -o value. Scripts can use that to separate "Azure said no" from "we called it wrong". The error message starts with the code in parentheses, which is what you search for in the documentation.

In an interview Mid

What is the difference between the Azure control plane and the data plane? Give examples.

Analogy: the control plane is the building's management office (build the safe, hand out keys); the data plane is opening the safe.

Why it matters: being Owner of the subscription lets you create and configure a vault, but reading a secret in it is Forbidden until you hold a data-plane role on the vault. Half of all Azure "Forbidden" surprises are this split.

Related habit: az account show before anything that writes, or --subscription in scripts.

Also asked: How do you make sure a CLI command runs against the right Azure subscription? · A script fails with AuthorizationFailed. What does the error tell you? · How do you sign in to Azure from a server that has no browser?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.