OnCallReady

Lesson 22.18 · Azure I: CLI, Identity & Data Planes · 12 min read

Groups, custom roles and just-in-time access

In plain words

Imagine a club with many members. Instead of giving each person their own key to the club room, the club says "everyone on the football team can use the changing room". When someone joins the team, they get access; when they leave, it's gone, and nobody has to hunt down keys. For the very special room, the trophy cabinet, nobody holds a key all the time: you ask for one for an hour, say why, and it's logged.

That's Azure access at scale. Role assignments go to Entra groups like shop-developers, and membership decides who gets them. Custom roles cover the cases where built-in ones are far too broad, like a "Node Pool Scaler" role. PIM gives eligible, time-limited access instead of standing Owner. Conditions let a pipeline assign only specific roles.

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>
# 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:

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.

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):

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

Why it helps

Individual role assignments are how organisations end up with hundreds of forgotten permissions, including for people who left. Once you're on a platform team, you'll design and review the group model and write the Terraform for it, and an auditor will ask how you know who has production access. Groups and PIM are the answer they want: "nobody has standing Owner in production" is a sentence that comes up in interviews and in audits.

The practical details matter during tickets too: a user just added to a group keeps getting 403 until they sign in again, because group claims are in the token. And a per-user role listing without --include-groups shows only direct assignments, which can lead you to the wrong conclusion.

FAQ

Why assign roles to groups instead of users?

Because access then follows membership. Onboarding is adding someone to the right groups, and offboarding removes every role at once, instead of hunting down individual assignments across subscriptions. Access reviews read a few group memberships instead of hundreds of assignments. It also keeps you well below the per-subscription limit on role assignments. The same applies to people in most cases; workloads usually get their own identities with direct assignments.

Why does a user still get 403 after I added them to a group?

Group memberships are carried as claims in the user's token, and the token they're using was issued before the change. Until they sign in again or the token is refreshed, Azure doesn't see the new group. az logout and az login fixes it for the CLI; in the portal, signing out and in. Add RBAC propagation of up to about 10 minutes on top, and a short wait is normal.

When should I create a custom role?

When the nearest built-in role is much broader than what's needed, for example a team that only needs to scale node pools but would otherwise get Contributor on the cluster. Define explicit actions, not wildcards, keep AssignableScopes narrow, and manage it in Terraform with azurerm_role_definition. The AuthorizationFailed error names the exact missing action, and az provider operation show --namespace <ns> lists them all.

What is PIM and do I need it?

Privileged Identity Management, part of Entra ID P2 or ID Governance, turns standing role assignments into eligible ones. You activate an eligible role for a limited time, giving a justification and optionally passing MFA or approval, and the activation is logged and expires by itself. You need it, or an equivalent, for dangerous roles in production: Owner, User Access Administrator, Key Vault Administrator. Daily low-risk access can stay standing.

What are RBAC conditions for?

They restrict what a role assignment allows beyond the role itself. On storage data roles, a condition can limit access to one container or to blobs with a specific index tag. On Role Based Access Control Administrator, a condition can restrict which roles the holder may assign, which lets a Terraform pipeline grant, say, Key Vault Secrets User and AcrPull without being able to make itself Owner. Not every role supports conditions.

In an interview Mid

Why should you assign Azure roles to groups rather than users, and how do you handle privileged access?

Groups: grant the role to a security group once, then manage membership. Onboarding = add to groups; offboarding = remove, and every role goes at once. Access reviews read memberships instead of hundreds of assignments, and you stay under the per-subscription assignment limit. Checking a person means checking their groups too: az role assignment list --assignee <id> --all --include-groups. A new member needs a new token - groups travel as claims in it.

Privileged access - just in time: with PIM (Privileged Identity Management) dangerous roles are eligible, not standing. You activate Owner on prod for a few hours, with a justification, MFA and optionally approval; it is logged and expires. "Nobody has standing Owner in production."

Narrow roles: a custom role when built-ins are too broad - explicit actions (found from the AuthorizationFailed error or az provider operation show), narrow AssignableScopes, in Terraform. And conditions: Role Based Access Control Administrator limited to assigning specific roles lets a pipeline grant access without being able to make itself Owner.

Also asked: What is Privileged Identity Management and how does it improve security? · When would you write a custom role, and how do you find the actions it needs? · How do you let a Terraform pipeline create role assignments without letting it escalate its own privileges?

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