Two years in, rg-shop-prod has 140 role assignments: people who left, people who changed teams, a bot with Contributor "for one afternoon". Nobody can say who has access to what. This lesson is the three tools that stop that from happening: groups, custom roles, and access you switch on only when you need it.
What you need to know already: 22.8 (users, groups, object IDs, Entra roles vs Azure roles), 22.13 (role definitions, actions, scopes, inheritance).
Assign to groups, manage membership
A security group is an Entra ID group used for access (as opposed to a mail list). Grant the role to the group once; then only membership changes.
az ad group create --display-name shop-developers --mail-nickname shop-developers
az ad group member add --group shop-developers --member-id <user-object-id>
az role assignment create --role Reader --assignee <group-object-id> --scope <rg id>
az ad group create- create a group;--display-nameits name,--mail-nicknamea required short name (used for email, even if you never mail it).az ad group member add --group <group> --member-id <object id>- add one member, by its object ID.- the role assignment is the one from 22.13, with the group as the assignee.
# after the three commands above
az role assignment list --scope /subscriptions/.../resourceGroups/rg-shop-prod -o table
Principal Role Scope
---------------- ------ ----------------------------------------------
shop-developers Reader /subscriptions/.../resourceGroups/rg-shop-prod
The effective access of a user is the union of their own assignments and those of every group they belong to (including nested groups - groups that are members of other groups). So:
- onboarding = add to the right groups; offboarding = remove from them, and every role goes at once
- an access review (the periodic "does everyone still need this?" check) reads group memberships, not hundreds of assignments
- one role assignment per group per scope, which also keeps you well under the per-subscription limit on assignments
Checking a person means checking their groups too:
az ad group member check --group shop-developers --member-id <user-object-id>
az role assignment list --assignee <user-object-id> --all --include-groups
member check answers true or false. --include-groups adds the assignments the user gets through groups; without it the per-user listing shows only direct assignments - one more way a listing lies by omission.
Membership changes also need a new token: the token carries the user's groups as claims (fields inside the token), so a freshly added member keeps getting 403 until they sign in again.
Custom roles
When no built-in role fits - usually because the nearest one is far too broad - define a custom role in a JSON file:
{
"Name": "Cluster Node Pool Scaler",
"Description": "Scale node pools, nothing else",
"Actions": [
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.ContainerService/managedClusters/agentPools/write"
],
"NotActions": [],
"DataActions": [],
"AssignableScopes": ["/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/rg-oncall-lab"]
}
(agentPools is ARM's name for node pools.)
az role definition create --role-definition @aks-scaler.json
az role definition list --custom-role-only true -o table
--role-definition @file reads the JSON from a file (the @ means "from this file"); --custom-role-only true lists only your own roles.
- AssignableScopes limits where it can be assigned. Keep it narrow.
- Prefer explicit actions over wildcards; a
*you add today silently grants every new action the resource provider adds tomorrow. - Finding action names:
az provider operation show --namespace Microsoft.ContainerServicelists every operation of a provider - or read the AuthorizationFailed error, which names the exact action that was missing. - Custom roles are directory objects shared by every subscription in their assignable scopes; manage them in Terraform (
azurerm_role_definition).
Just-in-time: PIM
Just-in-time access means you get a powerful role only for the hour you need it. In Azure that is Privileged Identity Management (PIM, part of the paid Entra ID P2 licence). It replaces standing assignments (always on) with eligible ones. You hold "Owner on prod, eligible"; to use it you activate it for a few hours, with a justification, optionally with someone's approval and MFA (multi-factor authentication: a second proof, like a phone prompt). Activations are logged and expire on their own.
Standing access for daily work (Reader on prod, Contributor on dev), eligible access for everything dangerous. In an interview, "nobody has standing Owner in production" is the sentence people want to hear.
Conditions
Some roles support conditions - extra rules attached to an assignment that narrow it further. Azure calls this ABAC (attribute-based access control: decisions based on properties of the request, not only the role):
- Storage data roles: "only blobs under the container
exports" or "only blobs with index tagproject=orders" (a tag set on individual files). - Role Based Access Control Administrator: "may only assign Key Vault Secrets User and AcrPull" - the safe way to let a Terraform pipeline create role assignments without handing it the power to make itself Owner.
az role assignment create --role "Role Based Access Control Administrator" \
--assignee <pipeline-sp> --scope <rg id> \
--condition "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {4633458b-17de-408a-b874-0445c86b69e6, 7f951dda-4ed3-4680-a7ca-43fe172d538d}))" \
--condition-version 2.0
Read the condition as: "either this is not a role-assignment write, or the role being assigned is one of these two". (The two GUIDs are Key Vault Secrets User and AcrPull. Role IDs are the same in every tenant for built-in roles.) The lab does not evaluate conditions (simulator); the syntax is the real one.
What you can now do
- Move access from individuals to a group without a gap.
- Write a custom role with only the actions a job needs, found from the error.
- Explain eligible vs standing access, and what a condition on an assignment adds.