$ cd ../writing

2026-09-11 · 12 min

Azure identity, from startup to enterprise

Permissions inherit downward and nothing lower takes them back. That one rule decides when direct assignments stop working, when you need security groups, and when you need PIM and administrative units. A maturity ladder with the symptoms that tell you which rung you are on.

Open the access control blade on any resource in a mature Azure estate and count the rows.

I did this recently on a single function app. Eighty-three role assignments. Seven of them Owner. Almost none of them created on that resource: they arrive from the resource group, from the subscription, from a management group two levels up. Two rows said Unknown next to a bare GUID, which is what a role assignment looks like after the identity it pointed at was deleted. One person appeared twice, once inherited from the subscription and once from the management group, with the same role both times.

Nothing there is broken. That estate works. Every one of those assignments was made by someone sensible solving a real problem on a Tuesday.

That is the thing about identity at scale: it does not fail, it accumulates. And the tooling shows you everything, honestly and in full, which turns out to be no help at all when the answer is eighty-three rows long.

This is the first of three pieces about what to do at each size. Part 2 takes security groups apart, Part 3 covers PIM and administrative units. This one is the map: how permissions actually travel, and how to tell which rung of the ladder you are standing on.

One rule explains most of it

Azure has two hierarchies, and almost nobody draws them together.

The resource side is the one everyone knows:

Tenant root
  └── management group
        └── management group (nested, optional)
              └── subscription
                    └── resource group
                          └── resource

The identity side sits beside it:

administrative unit
  └── security group
        └── role assignment
              └── PIM eligibility

Reading it downward: an administrative unit bounds who may administer the directory objects inside it. A security group decides who is in it. A role assignment says what they may do, and at which scope. A PIM eligibility decides whether they hold that permission or merely may activate it.

PIM is Privileged Identity Management, the Entra feature that makes a role something you switch on for a while rather than something you keep. It shows up at every rung above the first, so it is worth knowing the name early.

One rule connects them, and it is the only one you have to hold on to:

A role assignment applies at its scope and everywhere beneath it, and nothing lower in the tree takes it away.

Grant Reader at a management group and it reaches every subscription under it, every resource group in those, every resource in those. Forever, until someone removes it at the level where it was made.

There is exactly one mechanism that subtracts, deny assignments, and you will never create one. Azure creates them on behalf of a few managed services, deployment stacks being the one you are most likely to meet. They take precedence over everything including Owner, and they are blunt enough that reaching for one usually means the model above it is wrong.

Permissions flow one way, and they only add. That asymmetry is why identity gets harder with scale rather than merely bigger.

None of this is hidden. Every row in the access control blade is labelled with the scope it came from, marked (Inherited) where it was not made on the resource itself. Visibility was never the problem. Nobody opens the screen until something has already gone wrong.

Where to attach an assignment

Once inheritance is clear, the design question becomes obvious: at which scope do you grant?

Higher is fewer assignments and broader blast radius. Lower is precise and multiplies.

Take a platform team that needs read access everywhere. Grant it once per subscription:

management group
  ├── subscription A   <- granted here
  ├── subscription B   <- granted here
  └── subscription C   <- granted here

  3 assignments, +1 for every subscription you ever add

Or grant it once above them:

management group       <- granted here, once
  ├── subscription A      inherits
  ├── subscription B      inherits
  └── subscription C      inherits
      subscription D      inherits, the day it is created

  1 assignment, forever

Everyone works that out immediately. The part that gets missed is the second half: the same property that makes the second version scale is what makes it invisible. Nobody administering subscription D granted that access, nobody will remember it is there, and it shows up on that subscription’s access blade only if you read the inherited rows. Which is precisely how you end up with eighty-three of them.

The working rule: grant at the highest scope where the permission is genuinely correct for everything below it, and never higher. “Genuinely correct for everything below” is the part that requires thought, and it is a different answer for read than for write.

The ladder

Estates do not grow gradually. They cross thresholds, and at each threshold the previous approach stops working, usually quietly.

One thing first, because I had this wrong for a long time and it matters more than anything else here. The rung is not set by how many subscriptions you have. It is set by how many things need separating from each other: teams, products, environments with different blast radiuses, compliance boundaries with different auditors.

Three subscriptions holding nineteen microservices owned by several teams is not a small estate. It is a mid-sized one that happens to be packed tightly. Conway had it right about architecture mirroring organization, and access is the most literal case of it: every boundary that needs a different answer to “who may touch this” is a boundary your identity model has to represent, whether or not a subscription boundary exists in the same place.

So read the rungs as descriptions of how many independent answers you owe, not how many resources you own.

Rung one: small

One team, or a few people who all trust each other with everything. One product, or several that share the same blast radius. Whether that is one subscription or five does not much matter.

Identity looks like: users assigned roles directly, in the portal, by whoever has Owner. Often one person who holds Owner everywhere and remembers who has what.

This is the correct answer at this size. Groups, code and PIM would all be overhead against a set of facts one person can hold in their head. Anyone telling you to start with a governance framework before you have anything to separate is selling something.

The symptoms that you have outgrown it, any one of which is enough:

  • The person who remembers goes on holiday and access requests stop.
  • Someone leaves and their access is still live a month later, because removal is manual and nobody owns it.
  • You cannot answer “who can write to production” without clicking through several blades.
  • You catch yourself granting Owner because working out the right role takes longer than the task.

The move is rung two. The cost is a layer of indirection: access is no longer a fact about a person, it is a fact about a group they happen to be in.

Rung two: mid

Several teams who should not all have the same access. Environments that need different answers. Usually one or two management groups by now, organized by environment or by product line.

Identity looks like: security groups instead of users, assignments made at the management group rather than per subscription, and increasingly the groups themselves defined in code rather than clicked.

Three moves matter here, and the last two are the ones people skip.

Assign to groups, never to people. A role assignment to a user is a fact you have to remember. A role assignment to a group is a fact the group membership answers for you.

Let the directory decide who is in the group. A security group does not need a hand-maintained member list. A dynamic group takes a rule over user attributes, something close to company equals X and department in [list], and the directory keeps the membership current on its own. Somebody joins that department, they are in. Somebody leaves the company and is disabled in the directory, they are out, everywhere that group grants access, without anyone remembering to revoke. That last property is the whole reason to do it: leaver handling stops being a task and becomes a consequence.

Move the assignments up. Once groups exist, the same group assigned at twelve subscriptions is still twelve things to maintain. One assignment at the management group above them is one thing, and it covers subscription thirteen before it exists.

This is also the rung where automating the groups themselves starts paying for itself, and where you meet the first real wall. Managing groups from a pipeline means an app registration with directory permissions, and those permissions are a step up from what your resource-deployment identity holds. The tooling is easy. Getting the service principal the rights to run it is the part that takes a week and an approval.

What breaks next, and this is where most estates live:

  • Group sprawl. Nobody decided to have hundreds of groups; a formula did.
  • Naming drift. The convention exists in a document and nothing enforces it, so three conventions coexist for the same three roles.
  • Standing access. Everyone who ever needed production write still has it, because the group is the right shape and nothing expires.
  • Inheritance surprises. Someone grants at a management group to fix one subscription and reaches forty.

The move is rung three. The cost is friction, and it is real: rung three means people cannot do things they used to be able to do without asking first.

Rung three: enterprise

Multiple streams, product lines, or entire companies sharing one tenant. Auditors with names. The point where somebody outside your team has an opinion about who can reach production.

Identity looks like: nobody holds production write, several people are allowed to ask for it, and a policy rejects the group that does not match the naming convention before a human sees it. Privileged access is eligible rather than active, and directory administration is delegated per stream instead of held tenant-wide.

Three structural additions.

PIM, so that standing access stops existing. Instead of holding Contributor on production, you are allowed to become Contributor on production, with approval and a clock. You are nothing until you activate, and you go back to nothing when the window closes.

Administrative units, so directory administration can be delegated. Once several streams or companies share one tenant, “who may manage groups” needs a boundary. An administrative unit is that boundary: a scoped slice of the directory in which someone holds a directory role without holding it tenant-wide.

Custom roles, so the grant matches the job. Built-in roles are coarse by design. Contributor is “everything except granting access”, which is far more than most jobs need, and it is what people reach for because the alternative is reading a permissions list. A custom role with the dozen operations a job actually performs is more work once and less exposure permanently. It is also the point where least privilege stops being a slogan and becomes a JSON file somebody has to maintain.

One practical thing nobody warns you about: at this rung you probably stop having one account. Your everyday identity reads mail and joins meetings; a separate privileged account does the administration, and only that one is eligible for anything that matters. Different organizations name them differently, and the principle is identical everywhere. It is also mildly infuriating for the first month, because every tool you own now needs to know which of your two identities you meant.

What breaks at this rung is more interesting than what breaks at the others, because these are the failures of the end state everyone is aiming for:

  • Approval becomes a bottleneck, and bottlenecks get routed around. An eligibility nobody can activate at 2am is theatre.
  • PIM does not cover everything, and the gaps are not obvious.
  • Automation gets blocked by the controls it is meant to enforce. The service principal that manages your groups needs privileges that are themselves privileged.

Rung four: governed

There is a rung above the one I am standing on, and I am going to describe it honestly as somewhere we have not arrived.

Rung three answers “who may do what”. Rung four answers “and who checked, and when, and what happens when nobody has looked in a year”. The building blocks have names in Entra: access packages, which bundle the groups, roles and applications a job function needs so that joining a project is one request rather than seven; access reviews, which put an expiry on the question rather than on the grant, by asking an owner every quarter whether these forty people still need this; and lifecycle workflows, which drive joiner, mover and leaver from the HR record instead of from somebody remembering.

Underneath all three is the same idea, and it is the one genuinely new thing at this rung: access gets an owner and a review date, not just a grant date. Everything below rung four is about making the grant correct at the moment it is made. Rung four is about the years afterwards.

Which brings me to the honest part.

Those two rows in the opening, the ones saying Unknown next to a bare GUID, are still there. I know exactly what they are: role assignments whose principal was deleted, so the grant survives pointing at nothing. They are harmless. They grant access to an identity that cannot log in because it does not exist. Cleaning them up is a small, safe, entirely unglamorous task that has been available to me for months.

Nobody owns the eighty-three rows. That is the actual finding, and it is more specific than “we have not got round to it”.

Where PIM is configured, expiry is real and automatic: eligible assignments run out after a year, active ones after six months, and somebody has to ask again. That is a review process, and it works. But it only covers the paths PIM was pointed at. Every plain security group beside them, every role assignment made directly on a resource, every one of those eighty-three rows, has no expiry, no owner, and no date on which anyone is obliged to look. They do not rot loudly. They just stay.

So the estate has two populations: the privileged paths, which are governed, and everything else, which accumulates. The two dead rows are visible proof of which population is larger.

That gap is what rung four closes, and it is why I am writing this series rather than presenting it.

Reading your own estate

Two questions worth answering out loud, whatever rung you are on.

Where do your assignments actually live? Not where you think, where they are. On the resource, on the resource group, on the subscription, or above it. The distribution tells you whether you have a model or an accumulation, and it takes one command:

az role assignment list --scope "/subscriptions/<id>" --include-inherited \
  --query "[].{principal:principalName, role:roleDefinitionName, scope:scope}" -o table

Sort by the scope column and look at the shape of the output:

scope                                    reading
---------------------------------------  ------------------------------------
/                                        tenant root, granted once, reaches all
/providers/.../managementGroups/<name>   a hierarchy someone designed
/subscriptions/<id>                      per-subscription, multiplies
/subscriptions/<id>/resourceGroups/<rg>  precise, and invisible from above

If almost every row sits at the same scope as the thing you queried, you have an accumulation rather than a model, whatever the size of the estate. If most rows arrive from above, somebody designed this, and the next question is whether the design still matches who works here.

How many people can write to production right now, without asking anyone? Not how many should. How many can, today, including inherited assignments, including through group membership, including the service principals. If that number is not zero, rung three has something to offer you.

Whatever the answer, it is the number that decides which rung you are actually on, rather than the one you think you are on. The only way through is to keep answering it out loud.


Part 1 of three. Part 2 is about security groups at scale: how many you are heading for, why a naming convention nothing enforces is a suggestion, and the two ways group automation fails silently. Part 3 is about PIM and administrative units, and what they cost you.

azureidentityrbacgovernance

read next