OnCallReady

Lesson 22.29 · Azure I: CLI, Identity & Data Planes · 17 min read

Storage accounts, blobs, and who is allowed in

In plain words

Imagine a huge locker building. Each locker has a name on the door, and inside there are different kinds of compartments: some for boxes, some for shared shelves. The building manager has two master keys that open every locker forever. You can also make a temporary pass from a master key: "this person can open locker 7 until Friday". Or, the safer way, each person shows their ID card and the system checks their permissions every time.

A storage account like stsysoplab is that building: blobs, file shares, queues and tables inside one DNS name. The two account keys are the master keys, and they bypass RBAC completely. A SAS token is the temporary pass. Entra ID with a role like Storage Blob Data Reader is the ID card. The CLI quietly uses the master key unless you say --auth-mode login.

Terraform state (13.1), nightly backups, exported reports: they all end up as files in Azure Storage. Whoever can read that storage can read your secrets in state. And by default, far more people can read it than you would guess - through a master key most of them do not know they have. This lesson is about who is allowed in, and how to take the master key away.

What you need to know already: 22.1 (control plane vs data plane), 22.13 (roles and dataActions), 22.23 (the same Owner-is-not-data lesson for Key Vault), 13.4 (the Terraform azurerm backend stores state in a blob).

A storage account is Azure's general storage resource: a namespace with its own addresses (<name>.blob.core.windows.net and friends) holding four services:

Platform work touches blobs (Terraform state, backups, logs, build outputs) and files (shared volumes for Kubernetes, 22.31).

The account settings that matter

az storage account show -n <account> prints the account; this --query picks the settings worth checking:

$ az storage account show -n stsysoplab --query "{kind:kind, sku:sku.name, tier:accessTier, tls:minimumTlsVersion, publicBlobs:allowBlobPublicAccess, sharedKey:allowSharedKeyAccess, network:publicNetworkAccess}"
{
  "kind": "StorageV2",
  "network": "Enabled",
  "publicBlobs": false,
  "sharedKey": true,
  "sku": "Standard_LRS",
  "tier": "Hot",
  "tls": "TLS1_2"
}

Three ways to authenticate to blobs

account key       one of two 512-bit keys = full control of EVERYTHING in the account, forever
SAS token         a URL signed with a key: limited permissions, expiry, IP range
Entra ID          your token + a data-plane role (Storage Blob Data Reader/Contributor/Owner)

A SAS (shared access signature) is a URL with a signature appended: whoever has the URL may do what it allows until it expires. No login needed.

The account key is root. It bypasses RBAC completely: anyone with Contributor on the account (Microsoft.Storage/storageAccounts/listkeys/action) can fetch the key and read everything, whatever their data roles say. A SAS signed with that key is only as revocable as the key - rotate the key and every SAS dies. A user delegation SAS is signed with an Entra-derived key instead, so it respects RBAC and expires with it.

What the CLI does by default

az storage blob upload --account-name <account> -c <container> -n <blob name> -f <local file> uploads a file. Watch what happens when you give it no credentials:

Run a blob command with no credentials and the CLI goes and gets the key:

$ az storage blob upload --account-name stsysoplab -c reports -n r.txt -f r.txt

There are no credentials provided in your command and environment, we will query for account
key for your storage account.
It is recommended to provide --connection-string, --account-key or --sas-token in your command
as credentials.

You also can add `--auth-mode login` in your command to use Azure Active Directory (Azure AD)
for authorization if your login account is assigned required RBAC roles.
...
Finished[#############################################################]  100.0000%

It worked - through the key, because you are Owner. That warning is the CLI telling you it just used root. --auth-mode login uses your token and a data role instead:

$ az storage blob upload --account-name stsysoplab -c reports -n r.txt -f r.txt --auth-mode login
ERROR: You do not have the required permissions needed to perform this operation.
Depending on your operation, you may need to be assigned one of the following roles:
    "Storage Blob Data Owner"
    "Storage Blob Data Contributor"
    "Storage Blob Data Reader"
    ...
If you want to use the old authentication method and allow querying for the right account key,
please use the "--auth-mode" parameter and "key" value.

Same lesson as Key Vault: Owner is control plane. Data needs a data role.

Turn the keys off

az storage account update ... --allow-shared-key-access false switches the account keys (and every SAS signed with them) off:

az storage account update -n stsysoplab --allow-shared-key-access false

Afterwards the key path fails:

ERROR: Key based authentication is not permitted on this storage account.
ErrorCode: KeyBasedAuthenticationNotPermitted

and only Entra ID + RBAC works: every access is attributable to a principal, scoped, and revocable. Two things to check before flipping it on a real account: anything still using a connection string or SAS breaks (old apps, some Azure services, older Terraform backends - the azurerm backend supports use_azuread_auth = true), and you need the data roles in place first.

Containers and blobs

Create a container, upload, list, download - all with --auth-mode login:

az storage container create --account-name stsysoplab -n reports --auth-mode login
az storage blob upload   --account-name stsysoplab -c reports -n 2026/09/r.json -f r.json --auth-mode login
az storage blob list     --account-name stsysoplab -c reports --auth-mode login -o table
az storage blob download --account-name stsysoplab -c reports -n 2026/09/r.json -f /tmp/r.json --auth-mode login
Name    Blob Type  Blob Tier  Length  Content Type  Last Modified             Snapshot
------  ---------  ---------  ------  ------------  ------------------------  --------
r.txt   BlockBlob  Hot        6       text/plain    2026-09-24T09:40:07+00:00

"Folders" are just / in blob names (unless the account has a hierarchical namespace switched on - real directories, sold as ADLS Gen2, Azure's data-lake variant). An existing blob is not overwritten without --overwrite:

ERROR: The specified blob already exists.
ErrorCode:BlobAlreadyExists
If you want to overwrite the existing one, please add --overwrite in your command.

Container-level operations (create, list) are also authorised through Microsoft.Storage/storageAccounts/blobServices/containers/* actions - which Contributor and Owner do have. Blob reads and writes are dataActions. That is why you can create a container with --auth-mode login and then fail to put anything in it.

Terraform state lives here

The Terraform backend (13.4) is a blob with a lease (a lock on a blob that only one client can hold) for state locking. Everything in this lesson applies to it: it holds secrets in plaintext, so no public access, no shared keys if you can avoid them, versioning (every overwrite keeps the old version) and soft delete on, and only the pipeline's identity with Storage Blob Data Contributor on that one container.

What you can now do

Why it helps

Storage accounts hold things that matter to a platform team: Terraform state, backups, logs, build artifacts. They're also a favourite source of breaches: public blob containers, leaked connection strings, SAS tokens valid for years. Knowing that the account key is root, and that anyone with Contributor can list it, explains why security teams push to disable shared key access, and what breaks when you do.

It also explains confusing CLI behaviour: an upload that works without --auth-mode login because the CLI fetched the key for you, and then fails with --auth-mode login because you have no data role. Once you've seen it, you'll recognise it in pipelines, and you'll know how to lock down your own Terraform state account properly.

Commands in this lesson

az

FAQ

Why did my upload work without a data role?

Because without credentials the CLI fetches the account key using your control-plane permissions. Owner and Contributor include listkeys, and the key gives full access to all data, bypassing RBAC. The warning about querying for the account key tells you this happened. With --auth-mode login the CLI uses your Entra token instead, and then you need a data role like Storage Blob Data Contributor, which Owner doesn't include.

What's the difference between a SAS and a user delegation SAS?

A normal service or account SAS is signed with an account key: it grants whatever permissions and expiry it states, and the only way to revoke it early is to rotate that key, which kills every SAS signed with it. A user delegation SAS is signed with a key obtained through Entra ID, so it's tied to the permissions of the identity that created it, has a limited maximum lifetime, and stops working if that access is removed. Prefer user delegation SAS.

What breaks when I disable shared key access?

Anything that authenticates with an account key, a connection string or a key-signed SAS. That can include older applications, some Azure services and integrations, tools configured with connection strings, and Terraform backends not using use_azuread_auth = true. Afterwards those fail with KeyBasedAuthenticationNotPermitted. Inventory the clients first, move them to Entra ID with data roles, then flip the setting. The benefit is that every access is attributable and revocable.

Which redundancy option should I choose?

LRS keeps three copies in one datacentre, so a zone outage makes the data unavailable. ZRS spreads copies across three availability zones in the region and survives a zone failure. GRS adds an asynchronous copy in the paired region for regional disasters, and GZRS combines zone and geo redundancy. For Terraform state and anything important, ZRS at least; geo redundancy when you need a regional recovery option and accept its lag.

Why can I create a container but not upload a blob?

Because container management goes through Microsoft.Storage/storageAccounts/blobServices/containers/* actions on the control plane, which Contributor and Owner have, while reading and writing blobs are data actions that need a data role. So with --auth-mode login, an Owner can create a container and then get a permissions error putting anything in it. It's the same control plane versus data plane split as in Key Vault.

In an interview Mid

What are the ways to authenticate to Azure Blob Storage, and which do you prefer?

Watch the CLI: with no credentials, az storage blob commands quietly fetch the account key ("we will query for account key") - root. Use --auth-mode login; as Owner without a data role you then get a permissions error, because Owner is control plane.

To make Entra ID the only way in: data roles first, then az storage account update --allow-shared-key-access false (KeyBasedAuthenticationNotPermitted afterwards) - checking first what still uses keys or SAS. Plus allowBlobPublicAccess false.

Also asked: How would you secure the storage account that holds Terraform state? · Why is Contributor on a storage account effectively full data access? · What do the redundancy options LRS, ZRS and GRS mean?

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