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:
- blobs - files of any kind (a blob = "binary large object"), kept in containers - not Docker containers: here a container is just a folder-like bucket of blobs inside the account.
- files - network file shares (SMB/NFS, the protocols Windows and Linux use for shared drives).
- queues and tables - simple message queues and a key-value store.
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"
}
- Name: 3-24 lowercase letters and digits, globally unique (it is a DNS name).
- Redundancy (sku):
LRSthree copies in one datacentre;ZRSacross three availability zones (separate data centres in one region, with their own power and network);GRSLRS plus an async copy in the paired region (a second region Azure pairs with yours, e.g. westeurope with northeurope);GZRSboth. Zone failure takes out LRS data availability; ZRS survives it. - Access tier: Hot, Cool (30 days min), Cold (90), Archive (180, offline, hours to rehydrate, i.e. bring back online). The numbers are minimum days you pay for. Cheaper storage, dearer reads. Lifecycle management rules move blobs down the tiers by age automatically.
- allowBlobPublicAccess: whether containers may be made anonymous. Keep it false; public blobs are how data leaks make the news.
- allowSharedKeyAccess: whether the two account keys (and SAS tokens signed with them - next section) work at all.
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
- Name the three ways into blobs and why the account key is root.
- Use
--auth-mode loginand a data role instead of the key. - Switch shared key access off, knowing what breaks first.