Skip to content

Module 3.1: Microsoft Entra ID & Azure RBAC

Complexity: [MEDIUM] | Time to Complete: 2.5h | Prerequisites: Cloud Native 101. You will use this module after you can already read and create resources in Azure, because the module assumes comfort with role and identity fundamentals.

After completing this module, you will be able to:

  • Configure Entra ID application registrations, service principals, and Managed Identities for Azure workloads
  • Design Azure RBAC role assignments across Management Groups, Subscriptions, and Resource Groups with least privilege
  • Implement Conditional Access policies and Privileged Identity Management (PIM) for just-in-time access
  • Diagnose Entra ID vs Azure RBAC permission conflicts using role assignment analysis and access reviews

Hypothetical scenario: a platform team ships a new microservice with a client secret embedded in Helm values. Six months later, nobody remembers which pipeline created the app registration. The secret never rotated. An attacker with read access to the chart history now has Contributor on a production subscription. The breach is not a firewall failure. It is an identity lifecycle failure that compounded quietly.

This pattern is common because Azure makes resource creation easy while identity cleanup stays manual. Identity is not just security---it is the foundation of everything in Azure. Every VM, storage account, and Kubernetes cluster call flows through Entra authentication and Azure RBAC authorization. Traditional on-premises defenses often lean on network segmentation first. Azure’s control plane is identity-driven first. A compromised service principal with broad RBAC can delete data even when NSGs are perfect.

In this module, you will learn how Microsoft Entra ID (formerly Azure Active Directory) works as the identity backbone of Azure. You will map tenants, management groups, and subscriptions to real permission boundaries. You will separate Entra directory roles from Azure resource roles---a distinction that still trips experienced engineers during incidents. You will also learn when managed identities beat secrets, and when workload identity federation beats both. By the end, you can design least-privilege access that survives audits and reduces licensing waste.


Azure’s Identity Backbone: Microsoft Entra ID

Section titled “Azure’s Identity Backbone: Microsoft Entra ID”

Before you create a single resource in Azure, you need to understand the identity layer that governs everything. Microsoft Entra ID (still frequently called Azure AD in documentation, CLI tools, and everyday conversation) is a cloud-based identity and access management service. Think of it as the bouncer at every door in the Azure building. Every person, every application, and every automated process must present credentials to Entra ID before Azure will do anything.

The Bouncer Analogy

A nightclub bouncer checks ID at the door—that is authentication. The wristband color determines which rooms you may enter—that is authorization. Entra ID issues wristbands (tokens). Azure RBAC decides which rooms (resources) each wristband may access. You need both checks; a forged ID at the door is useless if the wristband never gets issued, and a valid ID without the right wristband still cannot enter the VIP room.

Cloud operators who internalize this analogy debug faster. Sign-in failures are bouncer problems (password, MFA, Conditional Access). Resource Manager AuthorizationFailed errors are wristband problems (missing RBAC, wrong scope, missing data-plane role, deny assignment). Mixing the two leads to futile password resets when the real issue is a missing Storage Blob Data Contributor assignment.

Entra ID is not the same as on-premises Active Directory Domain Services (AD DS). This is one of the most persistent misconceptions in the Azure world. AD DS uses Kerberos and LDAP for authentication, organizes objects into Organizational Units (OUs) with Group Policy Objects (GPOs), and requires domain controllers running on Windows Server. Entra ID uses OAuth 2.0, OpenID Connect, and SAML for authentication, has a flat structure (no OUs, no GPOs), and is a fully managed cloud service.

On-Premises AD DSMicrosoft Entra ID
Kerberos / NTLM / LDAPOAuth 2.0 / OIDC / SAML
Organizational Units (OUs)Flat directory (no OUs)
Group Policy Objects (GPOs)Conditional Access Policies
Domain Controllers (servers)Fully managed SaaS
Forest / Domain / Trust modelTenant model
On-prem network requiredInternet-accessible
Supports LDAP queriesSupports Microsoft Graph API

If your organization has on-premises AD DS and wants to use the same identities in Azure, you use Entra Connect (formerly Azure AD Connect) to synchronize identities. This hybrid setup is extremely common in enterprises.

A tenant is a dedicated instance of Entra ID that your organization receives when it signs up for any Microsoft cloud service (Azure, Microsoft 365, Dynamics 365). Think of a tenant as your organization’s apartment in a massive apartment building: you share the building infrastructure with others, but the tenant boundary isolates your users, groups, and applications. This is why tenant governance is often the first place teams secure before applying broader resource rules. Every tenant has a unique identifier (a GUID) and at least one verified domain; by default, you get a domain like yourcompany.onmicrosoft.com, but you can (and should) add your own custom domain to make identities and sign-in patterns feel like your organization rather than a generic cloud namespace.

Terminal window
# View your current tenant
az account show --query '{TenantId:tenantId, SubscriptionName:name}' -o table
# List all tenants your account has access to
az account tenant list -o table
# Show detailed tenant information
az rest --method GET --url "https://graph.microsoft.com/v1.0/organization" \
--query "value[0].{DisplayName:displayName, TenantId:id, Domains:verifiedDomains[].name}"

The Hierarchy: Management Groups, Subscriptions, and Resource Groups

Section titled “The Hierarchy: Management Groups, Subscriptions, and Resource Groups”

Azure organizes resources in a four-level hierarchy. Understanding this hierarchy is essential because access control and policy inheritance flow from top to bottom. Once you internalize it, you can predict not only where permissions land, but also where controls should be applied for least-privilege governance.

flowchart TD
Root["Root Management Group<br><i>(Created when first user opts into Mgmt Groups)</i>"]
ProdMG["MG: Production"]
NonProdMG["MG: Non-Production"]
ProdSub["Sub: Prod-App1"]
DevSub["Sub: Dev"]
WebAppRG["<b>RG: webapp</b><br>- App Svc<br>- SQL DB"]
SandboxRG["<b>RG: sandbox</b><br>- VM<br>- VNet"]
Root --> ProdMG
Root --> NonProdMG
ProdMG --> ProdSub
NonProdMG --> DevSub
ProdSub --> WebAppRG
DevSub --> SandboxRG

Inheritance flows DOWN: Policy assigned at MG: Production → applies to ALL subscriptions, resource groups, and resources beneath it.

LevelPurposeKey Facts
Management GroupOrganize subscriptions into governance hierarchiesUp to 6 levels deep (excluding root). Max 10,000 MGs per tenant.
SubscriptionBilling boundary and access control boundaryEach subscription trusts exactly one Entra ID tenant. Max 980 resource groups per sub.
Resource GroupLogical container for related resourcesResources can only exist in one RG. Deleting RG deletes ALL resources inside.
ResourceThe actual thing (VM, database, storage account)Inherits RBAC and policy from all levels above.

A common mistake is treating subscriptions as purely a billing construct. They are also security boundaries, because a user with Owner on Subscription A has zero access to Subscription B by default, even if both subscriptions are in the same tenant. This is why large organizations use multiple subscriptions to isolate environments and teams: they want predictable blast-radius boundaries and cleaner privilege models before cost structures become a secondary consideration.

Enterprise Layout: Tenants, Management Groups, and Landing Zones

Section titled “Enterprise Layout: Tenants, Management Groups, and Landing Zones”

Real enterprises rarely run “one subscription for everything.” A common pattern is a platform subscription for shared services (logging, DNS hubs, identity automation) and workload subscriptions per product or environment (dev/test/prod). Management groups apply policies once—require tags, deny public IPs in production, inherit Azure Policy initiatives—while subscriptions keep billing and RBAC isolation crisp.

PatternStructureIdentity benefit
Environment isolationMG Production / NonProd with separate subsCA policies differ; prod Owners are fewer
Team isolationSubscription per product teamBlast radius contained to one sub
Platform hubShared services sub with user-assigned MIsCentral MI for DNS/ACR/Key Vault access
SandboxLow-privilege sub with Contributor for engineersNo standing Owner; PIM for exceptions

When designing landing zones, decide where human access lives (groups at MG or sub scope) versus where workload identities live (user-assigned MIs in the workload sub, federated CI/CD from GitHub). Hypothetical scenario: a company places all production apps in one subscription to “save money,” then grants 40 engineers Contributor. Incident response becomes role-assignment archaeology. Splitting subscriptions costs little compared to one mistaken az group delete.

Landing zone identity automation often uses infrastructure-as-code to create groups, assign RBAC, and register federated credentials. The operator still must understand scope strings. A role assignment scope of /providers/Microsoft.Management/managementGroups/Production affects every child subscription. A scope of /subscriptions/{id}/resourceGroups/app-rg affects only that group. Terraform azurerm_role_assignment and Bicep Microsoft.Authorization/roleAssignments must mirror the same paths you would type in az role assignment create.

Pause and predict: If you assign a user the ‘Contributor’ role at the Subscription level, but explicitly assign them the ‘Reader’ role at a specific Resource Group level within that subscription, what effective permissions do they have on the Resource Group? Answer: They have ‘Contributor’ access. Azure RBAC is an additive model. Permissions flow down the hierarchy, and a lower-level assignment cannot subtract or restrict permissions granted at a higher level (unless you use Azure Blueprints or explicit Deny Assignments, which are advanced and rare).

Terminal window
# List management groups
az account management-group list -o table
# Create a management group
az account management-group create --name "Production" --display-name "Production Workloads"
# Move a subscription under a management group
az account management-group subscription add \
--name "Production" \
--subscription "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# List subscriptions in the current tenant
az account list -o table --query '[].{Name:name, Id:id, State:state}'

Identities in Entra ID: Users, Groups, Service Principals, and Managed Identities

Section titled “Identities in Entra ID: Users, Groups, Service Principals, and Managed Identities”

Now that you understand the organizational structure, let’s dive into the identity types. In Azure, there are fundamentally two categories of identities: human identities (people who log in) and workload identities (applications and services that authenticate programmatically). That distinction is the first mental model shift: humans need interactive and audit-friendly access paths, while workloads need non-interactive identity patterns that scale safely in automation.

An Entra ID user represents a person. Users can be cloud-only (created directly in Entra ID) or synced (synchronized from on-premises AD DS via Entra Connect). Users can also be guest users, invited from other Entra ID tenants or external email providers via B2B collaboration. This is useful in real teams because production security depends on separating what “who is in the directory” means from “what each identity can actually touch” across scopes.

Terminal window
# Create a cloud-only user
az ad user create \
--display-name "Alice Engineer" \
--user-principal-name "alice@yourcompany.onmicrosoft.com" \
--password "TemporaryP@ss123!" \
--force-change-password-next-sign-in true
# List all users (first 10)
az ad user list --query '[:10].{Name:displayName, UPN:userPrincipalName, Type:userType}' -o table
# Get a specific user
az ad user show --id "alice@yourcompany.onmicrosoft.com"

Groups simplify access management. Instead of assigning roles to individual users, you assign roles to groups and add users to the groups so membership becomes the control point. This is how you avoid per-user sprawl, especially when teams change. Entra ID has two group types:

  • Security groups: Used for managing access to resources. This is what you’ll use 90% of the time.
  • Microsoft 365 groups: Used for collaboration (shared mailbox, SharePoint site, Teams channel). Also usable for RBAC, but carry extra baggage.

Groups can have assigned membership (manually add/remove members) or dynamic membership (members are automatically added/removed based on user attributes like department or job title). Dynamic groups require Entra ID P1 or P2 licensing.

Guest users (B2B collaboration) invite external identities into your tenant as userType: Guest. Guests authenticate with their home organization but appear in your directory for RBAC and app access. Treat guests like employees for access reviews—contractors often retain group memberships after projects end. Assign RBAC to groups that include guests only when necessary; prefer app-scoped access or entitlement management for vendor scenarios.

Group strategyBest forRBAC tip
Security group (assigned)Stable teams with manual onboardingPlatform-Ops group gets Contributor on rg-platform
Security group (dynamic)Department-based access at scaleRule department -eq "Engineering"; needs P1
Role-assignable groupNesting Entra directory rolesLimited to ~500 role-assignable groups per tenant
Microsoft 365 groupCollaboration + optional RBACExtra M365 licensing surface; use security groups for pure RBAC
Terminal window
# Create a security group
az ad group create \
--display-name "Platform Engineers" \
--mail-nickname "platform-engineers"
# Add a user to a group
USER_ID=$(az ad user show --id "alice@yourcompany.onmicrosoft.com" --query id -o tsv)
GROUP_ID=$(az ad group show --group "Platform Engineers" --query id -o tsv)
az ad group member add --group "$GROUP_ID" --member-id "$USER_ID"
# List group members
az ad group member list --group "Platform Engineers" --query '[].displayName' -o tsv

A service principal is the identity that an application uses to authenticate with Entra ID, and it is the object you actually grant permissions to. But there is a subtle two-step process that confuses many people:

  1. App Registration: A global definition of your application. Think of it as the blueprint. It lives in your home tenant and defines what permissions the app needs, what redirect URIs it uses, and what credentials it has.
  2. Service Principal (Enterprise Application): A local instance of the app in a specific tenant. Think of it as an installation of the blueprint. When you grant an app access to resources, you are granting access to the service principal, not the app registration.
flowchart TD
AppReg["<b>App Registration</b><br><i>(Blueprint / Template)</i><br><br>- Application (client) ID<br>- Redirect URIs<br>- API Permissions declared<br>- Client secrets / certificates<br><br><b>Lives in: Home tenant only</b>"]
SP["<b>Service Principal</b><br><i>(Enterprise Application)</i><br><br>- Object ID (unique per tenant)<br>- Role assignments (Azure RBAC)<br>- Consent grants (API permissions)<br><br><b>Lives in: Each tenant that uses the app</b>"]
AppReg -->|Creates| SP
Terminal window
# Create an app registration (automatically creates service principal in home tenant)
az ad app create --display-name "my-cicd-pipeline"
# Get the app ID
APP_ID=$(az ad app list --display-name "my-cicd-pipeline" --query '[0].appId' -o tsv)
# Create a client secret (AVOID THIS -- use federated credentials or certificates instead)
az ad app credential reset --id "$APP_ID" --years 1
# Preferred: Create federated credential for GitHub Actions (OIDC -- no secrets!)
az ad app federated-credential create --id "$APP_ID" --parameters '{
"name": "github-actions-main",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:myorg/myrepo:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'

Security note: Exposed client secrets in public repositories can be discovered quickly, and a high-privilege service principal can be abused for costly or destructive activity. The safer pattern is to avoid long-lived secrets by using Managed Identities for Azure workloads and federated workload identity for CI/CD.

Application Permissions vs Delegated Permissions

Section titled “Application Permissions vs Delegated Permissions”

App registrations declare API permissions in two flavors. Delegated permissions apply when a signed-in user is present—the app acts on behalf of that user within the consent boundary. Application permissions apply when the app calls APIs with its own identity (daemon jobs, unattended sync)—these are powerful and often require admin consent. Microsoft Graph and Azure Resource Manager permissions are separate from Azure RBAC: granting Directory.Read.All on Graph does not grant Reader on a subscription. Operators must track both planes during security reviews.

A mature registration workflow documents: which secrets or federated credentials exist, which API permissions are granted, which enterprise application (service principal) holds Azure RBAC assignments, and who owns quarterly review. Hypothetical scenario: an inventory scanner holds Graph application permission and subscription Reader via RBAC. Removing Graph access does not remove storage enumeration if RBAC Reader remains—fix both or the scanner still exfiltrates metadata.

Terminal window
# List API permissions declared on an app registration
az ad app show --id "$APP_ID" --query "requiredResourceAccess" -o json
# Show service principal RBAC (Azure plane) vs Graph app roles (directory/API plane)
az role assignment list --assignee "$SP_OBJECT_ID" --all -o table

A Managed Identity is a special type of service principal that Azure manages for you. You typically do not need to see or handle credentials---Azure automatically provisions, rotates, and revokes the tokens behind the scenes. This is the single most important identity concept for application developers on Azure.

There are two types, and the choice changes how you think about lifecycle and access sharing. This decision is not cosmetic because it determines whether identities stay coupled to a single resource or can be reused across environments.

FeatureSystem-AssignedUser-Assigned
Lifecycle[Tied to the resource (delete VM = delete identity)Independent (persists until you delete it)](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview)
SharingOne-to-one (each resource gets its own)One-to-many (multiple resources share one)
CreationEnable on the resourceCreate separately, then assign to resources
NamingNamed after the resourceYou choose the name
Best forSingle-resource scenarios, simpler managementShared access patterns, pre-provisioning
RBAC managementRole assignments per resourceRole assignments per identity (shared)
Terminal window
# Enable system-assigned managed identity on a VM
az vm identity assign --resource-group myRG --name myVM
# Create a user-assigned managed identity
az identity create --resource-group myRG --name "app-identity"
# Assign the user-assigned identity to a VM
IDENTITY_ID=$(az identity show -g myRG -n "app-identity" --query id -o tsv)
az vm identity assign --resource-group myRG --name myVM --identities "$IDENTITY_ID"
# Grant the managed identity access to a Key Vault
PRINCIPAL_ID=$(az identity show -g myRG -n "app-identity" --query principalId -o tsv)
az role assignment create \
--assignee "$PRINCIPAL_ID" \
--role "Key Vault Secrets User" \
--scope "/subscriptions/<sub-id>/resourceGroups/myRG/providers/Microsoft.KeyVault/vaults/myVault"

When your application code runs on a resource with a Managed Identity, it can acquire tokens without any credentials:

# Python example using azure-identity SDK
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
# DefaultAzureCredential automatically detects Managed Identity
credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://myvault.vault.azure.net/", credential=credential)
secret = client.get_secret("database-connection-string")
print(f"Secret value: {secret.value}")

The DefaultAzureCredential class tries several credential types, and the exact chain depends on SDK version and configuration. In Azure, Managed Identity is one built-in option; on a developer machine, local credentials such as Azure CLI can be used instead.

How Managed Identities Obtain Tokens Without Secrets

Section titled “How Managed Identities Obtain Tokens Without Secrets”

When code on an Azure resource calls the local Instance Metadata Service (IMDS) endpoint, Azure validates that the request originates from that resource and returns a Microsoft Entra access token for the managed identity’s service principal. No client secret or certificate is stored in your application configuration; Azure rotates credentials on the platform side. This is why managed identities are the default recommendation for App Service, Azure Functions, VMs, and AKS pods that call Azure APIs: you grant RBAC on the identity’s principalId, and the runtime handles token acquisition.

The operator workflow is deliberately simple: create or enable the identity, assign roles at the narrowest scope, then let the SDK call DefaultAzureCredential() or ManagedIdentityCredential(). Propagation delays still apply after role assignment, so automated deployments should retry token acquisition for several minutes after az role assignment create.

Workload Identity Federation for AKS, GitHub Actions, and External Workloads

Section titled “Workload Identity Federation for AKS, GitHub Actions, and External Workloads”

Not every workload runs on Azure compute with IMDS available. CI/CD pipelines in GitHub Actions, pods on Amazon EKS or Google GKE, and legacy VMs outside Azure still need Entra tokens—but client secrets expire, leak in logs, and resist rotation at scale. Workload identity federation lets a user-assigned managed identity or app registration trust tokens from an external identity provider (IdP) such as GitHub, Kubernetes OIDC issuers, or Google Cloud. You configure a federated identity credential with issuer, subject, and audience values that must case-sensitively match the external JWT; Microsoft identity platform validates the external token and issues an Entra access token with no long-lived secret.

sequenceDiagram
participant W as External workload
participant IdP as External IdP (GitHub/K8s)
participant Entra as Microsoft identity platform
participant Az as Azure resource API
W->>IdP: Request OIDC token
IdP->>W: JWT (subject matches federated credential)
W->>Entra: Exchange JWT for access token
Entra->>Entra: Validate trust on MI or app registration
Entra->>W: Entra access token
W->>Az: Call API with Bearer token

For AKS, the supported pattern is Azure Workload Identity. Enable the OIDC issuer on the cluster first. Create a user-assigned managed identity in the node resource group or a dedicated identity RG. Add a federated credential whose issuer is the cluster OIDC URL and whose subject is the Kubernetes service account (system:serviceaccount:<namespace>:<name>). Annotate the pod service account with azure.workload.identity/client-id. The pod receives Entra tokens like a VM managed identity, without mounting secrets. This curriculum targets Kubernetes 1.35; workload identity is the modern replacement for the deprecated aad-pod-identity addon pattern.

For GitHub Actions, you create a federated credential on the app registration (as shown earlier with az ad app federated-credential create) and use the azure/login action with client-id, tenant-id, and subscription-id—no AZURE_CLIENT_SECRET. Hypothetical scenario: a platform team runs twenty repositories; rotating twenty secrets quarterly is error-prone, but one federated trust per repo branch pattern scales with policy-as-code reviews.

Important constraint: tokens issued by Microsoft Entra ID cannot be used as input to federated identity credential flows—the external IdP must be GitHub, your Kubernetes issuer, AWS Cognito, etc., not another Entra tenant token chained informally.

Terminal window
# Federated credential on a user-assigned managed identity (AKS-style issuer example)
az identity federated-credential create \
--name "aks-federated-credential" \
--identity-name "app-identity" \
--resource-group myRG \
--issuer "https://oidc.prod.aks.azure.com/<tenant-id>/<cluster-id>/" \
--subject "system:serviceaccount:default:my-app-sa" \
--audience "api://AzureADTokenExchange"

Azure RBAC vs Entra ID Roles: The Great Confusion

Section titled “Azure RBAC vs Entra ID Roles: The Great Confusion”

This is the section that will save you hours of frustration. Azure has two separate role systems that operate at different layers, and confusing them is one of the most common mistakes in the Azure ecosystem. Once this split is clear in your head, you stop over-assigning permissions because you can immediately ask whether a request is directory-level or resource-level before you grant access.

These roles control access to Entra ID itself---the directory. They govern who can create users, manage groups, register applications, configure Conditional Access policies, and perform other directory operations. In other words, Entra ID roles are about identity administration and tenant governance, not direct resource creation, deletion, or data plane operations. Directory roles are assigned at tenant scope only; you cannot assign “User Administrator” on a single subscription because the directory is one flat namespace per tenant.

Examples of core Entra ID directory roles are shown below:

  • Global Administrator: Full access to Entra ID and all Microsoft services (the “root” of your tenant)
  • User Administrator: Can create and manage users and groups
  • Application Administrator: Can manage app registrations and enterprise applications
  • Security Reader: Read-only access to security features
  • Privileged Role Administrator: Can manage role assignments for other Entra directory roles (high blast radius—often paired with PIM)

When an engineer asks for “admin access,” clarify which plane they need. Directory admins can reset passwords and create app registrations; they cannot deploy VMs until someone grants Azure RBAC. Resource owners can delete storage accounts but cannot invite guest users unless they also hold directory roles.

These roles control access to Azure resources---VMs, storage accounts, databases, networks, and everything else you deploy. They operate on the management group / subscription / resource group / resource hierarchy. In practice, this means the scope you choose for a role assignment is often the difference between a secure design and a permission leak when teams grow or move projects between environments.

The four fundamental built-in roles are the practical starting point for most permission models because they cover most operational scenarios without overengineering.

RoleWhat It Can DoScope
OwnerFull access + can assign roles to othersCan manage everything and delegate
ContributorFull access to resources, but cannot assign rolesCan create/delete/modify resources
ReaderView-only accessCan see resources but not change anything
User Access AdministratorCan only manage role assignmentsCan grant/revoke access but not modify resources
flowchart LR
subgraph Tenant ["Entra ID Tenant"]
direction LR
EntraRoles["<b>Entra ID Roles (Directory Scope)</b><br>Global Admin, User Admin, App Admin, etc.<br><br><i>Controls: Users, Groups, Apps, Policies</i>"]
RBACRoles["<b>Azure RBAC Roles (Resource Scope)</b><br>Owner, Contributor, Reader, custom roles<br><br><i>Controls: VMs, Storage, Networks, Databases</i><br><br>Scope: Management Group → Subscription → Resource Group → Resource"]
end

KEY INSIGHT: Global Administrator does NOT automatically have Azure RBAC access. They must “elevate” themselves first, because directory privileges and Azure resource permissions are deliberately separated.

This is a critical detail: a Global Administrator in Entra ID does not automatically have Owner or Contributor access to Azure subscriptions. They can elevate themselves to get User Access Administrator at the root scope, but it is not automatic. Conversely, an Owner on an Azure subscription cannot create or manage Entra ID users.

Stop and think: A new security engineer is granted the ‘Global Administrator’ role in Entra ID. When they log into the Azure portal, they cannot see any Virtual Machines or Storage Accounts. Why? Answer: Global Administrator is a directory role that grants control over Entra ID (users, groups, policies), not an Azure RBAC role. The engineer has no default access to Azure resources. They must explicitly elevate their access to gain the User Access Administrator role at the root scope before they can grant themselves permissions to view or manage Azure resources.

Role Assignment Scope Hierarchy and az role assignment create

Section titled “Role Assignment Scope Hierarchy and az role assignment create”

Azure RBAC assignments attach a role definition to a security principal (user, group, service principal, or managed identity) at a scope. Scopes form a tree: management group → subscription → resource group → individual resource. Permissions are additive as they inherit downward; a Contributor at subscription scope can modify every resource group underneath unless a deny assignment blocks specific actions.

The CLI shape you will use daily is:

Terminal window
az role assignment create \
--assignee "<object-id-or-upn>" \
--role "<RoleName>" \
--scope "/subscriptions/<sub-id>/resourceGroups/<rg-name>"

Use the principal’s object ID for service principals and managed identities (principalId from az identity show or az ad sp show). For users, UPN often works, but object ID avoids ambiguity when guest accounts share display names. Scope should be as narrow as the task allows: prefer resource group over subscription, and resource over resource group when the workload is single-purpose.

Terminal window
# List Azure RBAC role assignments at subscription scope
az role assignment list --scope "/subscriptions/<sub-id>" -o table
# List assignments for one principal across the tenant (diagnostics)
az role assignment list --assignee "$PRINCIPAL_ID" --all -o table
# List all built-in Azure RBAC roles
az role definition list --query "[?roleType=='BuiltInRole'].{Name:roleName, Description:description}" -o table
# Show what a specific role can do
az role definition list --name "Contributor" --query '[0].{Actions:permissions[0].actions, NotActions:permissions[0].notActions}'

Deny Assignments and the Least-Privilege Model

Section titled “Deny Assignments and the Least-Privilege Model”

Least privilege on Azure means granting the minimum RBAC and directory roles required for a task, at the narrowest scope, for the shortest duration practical. Groups should hold roles; humans should be members of groups. Standing Global Administrator or subscription Owner assignments should be rare and visible in access reviews.

Deny assignments are the exception mechanism: they block specified control-plane or data-plane actions even when a role assignment would allow them. You cannot create arbitrary deny assignments yourself in most tenants—Azure creates them to protect platform resources, and deployment stacks can create deny settings to prevent deletion of managed resources. Operators still need to recognize deny assignments when troubleshooting “I have Contributor but the API returns AuthorizationFailed.” List them in the portal under Access control (IAM) → Deny assignments, or query with appropriate Microsoft.Authorization/denyAssignments/read permission.

Deny does not replace good role design. If teams rely on deny to compensate for excessive Contributor grants, the permission model is already unhealthy. The sustainable pattern is: built-in or custom roles scoped tightly, PIM for elevation, Conditional Access for sign-in risk, and access reviews to remove stale assignments.

When built-in roles are too broad or don’t fit your needs, you create custom roles. A custom role is a JSON definition that specifies exactly which actions are allowed or denied.

{
"Name": "VM Operator",
"Description": "Can start, stop, and restart VMs but not create or delete them",
"Actions": [
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/powerOff/action",
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/instanceView/read",
"Microsoft.Resources/subscriptions/resourceGroups/read"
],
"NotActions": [],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}
Terminal window
# Create a custom role from a JSON file
az role definition create --role-definition @vm-operator-role.json
# Assign the custom role
az role assignment create \
--assignee "alice@yourcompany.onmicrosoft.com" \
--role "VM Operator" \
--scope "/subscriptions/<sub-id>/resourceGroups/production"
# List custom roles in your subscription
az role definition list --custom-role-only true -o table

An important distinction: Actions vs DataActions. Actions control management plane operations (creating, deleting, configuring resources). DataActions control data plane operations (reading blobs in storage, sending messages to a queue). The role Storage Blob Data Reader uses DataActions, not Actions, because it grants access to the data inside storage, not to the storage account management operations.

Diagnosing “Access Denied” Across Entra ID and Azure RBAC

Section titled “Diagnosing “Access Denied” Across Entra ID and Azure RBAC”

When authentication succeeds but authorization fails, split the problem into token issuance (Entra sign-in, Conditional Access, app consent) versus RBAC evaluation (role assignments, scope, deny assignments, data-plane roles). The Azure portal’s “Check access” blade on a resource is the fastest sanity check for RBAC. For directory tasks, verify Entra directory role assignments in Entra admin center roles and administrators.

SymptomLikely planeFirst checks
Cannot sign in to portalEntra authentication / CASign-in logs, CA report-only, MFA registration
Sign-in works, cannot create RGAzure RBACaz role assignment list --assignee, scope too narrow
Can create container, cannot upload blobData-plane RBACMissing Storage Blob Data Contributor or equivalent
Global Admin cannot see VMsPlane confusionRBAC not granted; elevate access or assign Reader
Contributor but delete blockedDeny assignment / locksDeny assignments tab, CanNotDelete lock on RG
MI works locally, fails in AzureWrong credential chainDefaultAzureCredential picking CLI instead of IMDS

Worked troubleshooting flow: confirm the principal’s objectId, list all role assignments with --all, verify each assignment scope includes the target resource, confirm data-plane roles exist for storage/SQL/Key Vault data APIs, wait ten minutes for propagation, then inspect deny assignments. If the principal is a service principal, also verify API permissions and admin consent on the app registration—Graph consent gaps will not show up in az role assignment list.

Terminal window
# Effective permissions simulation (subscription scope example)
az role assignment list --assignee "$PRINCIPAL_ID" --scope "/subscriptions/<sub-id>" -o table
# Sign-in diagnostics for human users (requires appropriate Graph permissions)
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$top=5" \
--query "value[].{User:userPrincipalName, App:appDisplayName, Status:status.errorCode}" -o table

Conditional Access: Context-Aware Security

Section titled “Conditional Access: Context-Aware Security”

Conditional Access policies are the enforcement engine of Entra ID. They evaluate conditions (who, where, what device, what app) and make access decisions (allow, block, require MFA). Think of them as programmable if/then rules for authentication that let you tighten control based on context instead of trying to secure every login with one static policy. In practice, this gives you better risk balance because you can keep the default experience lightweight while still protecting high-risk scenarios.

Policy Anatomy: Signals, Conditions, and Grant Controls

Section titled “Policy Anatomy: Signals, Conditions, and Grant Controls”

Every Conditional Access policy is a pipeline. Assignments define who and what the policy applies to (users, groups, roles, cloud apps, or user actions such as registering security info). Conditions filter the sign-in context: client app type, device platform, location (IP ranges from named locations), sign-in risk level, or device compliance state. Access controls decide the outcome: block, grant with requirements (MFA, compliant device, approved app), or session controls (sign-in frequency, persistent browser session). Policies evaluate in report-only mode first, then enforcement—a practical rollout pattern that avoids locking out administrators on day one.

Policy stageWhat you configureOperator intent
Assignments (WHO/WHAT)Users, groups, roles, cloud apps, user actionsLimit blast radius—start with admins, then expand
Conditions (signals)Locations, device state, risk, client appsTie controls to real risk (unknown geography, legacy auth)
Grant controlsRequire MFA, require compliant device, blockEnforce step-up only when context warrants it
Session controlsSign-in frequency, app-enforced restrictionsReduce token lifetime for sensitive portals

Named locations are CIDR ranges or countries you label as trusted corporate networks versus “anywhere else.” Pairing “Require MFA except from trusted locations” with “Block legacy authentication” closes common password-spray paths without punishing employees on VPN. Risk-based Conditional Access (sign-in risk, user risk) requires Microsoft Entra ID Protection, which needs P2 licensing.

Assignments (WHO)Conditions (WHERE/WHEN/HOW)Controls (THEN WHAT)
Users/GroupsSign-in risk (AI-based)Block access
AppsDevice platform (iOS, etc.)Require MFA
RolesLocation (IP ranges)Require device
Client app typeSession limits
Device stateApp controls

Common Conditional Access patterns are usually implemented first as a baseline because they provide strong control with minimal policy complexity before more specialized scenarios:

  1. Require MFA for all Global Administrators (this should be your first policy)
  2. Block sign-ins from countries where you have no employees
  3. Require compliant devices for accessing sensitive applications
  4. Force re-authentication every 4 hours for Azure portal access

Conditional Access requires at least Entra ID P1 licensing. You can view and manage policies through the Azure portal or Microsoft Graph API:

Terminal window
# List Conditional Access policies via Graph API
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
--query "value[].{Name:displayName, State:state}"

Multi-factor authentication is not a single technology. Entra supports Authenticator push/OTP, FIDO2 security keys, certificate-based auth, and telephony (where licensed). Security defaults favor Microsoft Authenticator for all users, while P1/P2 tenants can tune allowed methods per Conditional Access policy. Phishing-resistant MFA (FIDO2, Windows Hello for Business, certificate-based) should be required for Global Administrators and for PIM activations.

Operators should align MFA policy with helpdesk capacity. If every CA policy demands FIDO2 on day one, contractors without enrolled keys will open tickets. A staged rollout—Authenticator first, FIDO2 for privileged roles—is usually sustainable. Pair MFA requirements with named locations so corporate VPN users are not challenged on every API call, while unknown locations always step up.

Break-glass emergency access accounts should be cloud-only, excluded from routine Conditional Access, and monitored with alerts. Skipping this step causes painful lockouts during MFA migrations when administrators cannot sign in to fix policies.


Privileged Identity Management (PIM) & Access Reviews

Section titled “Privileged Identity Management (PIM) & Access Reviews”

Even with least privilege and Conditional Access, standing access is a massive security risk. Standing access means a user can hold a highly privileged role like Owner or Global Administrator continuously, even when they are not actively performing privileged tasks. If their account is compromised, the attacker can immediately inherit that privilege. This is why modern governance patterns treat privileged assignments as exceptions, not defaults.

Microsoft Entra Privileged Identity Management (PIM) solves this by providing Just-In-Time (JIT) access that limits privileged exposure to specific windows. With PIM, users are made eligible for a role rather than being permanently assigned to it. When they need to perform a privileged task, they must activate the role and satisfy the controls defined in the policy, which is usually a lower-risk posture than indefinite standing rights.

An active assignment behaves like a classic standing role: the principal has the privilege until someone removes it. An eligible assignment means the principal may request activation; until activation succeeds, the effective permissions are not granted. PIM applies to both Entra directory roles and Azure resource roles when licensed appropriately—Global Administrator eligible assignments are directory-scoped, while subscription Owner eligible assignments are Azure RBAC-scoped. Activation creates a time-bound active assignment; when the window expires, the extra assignment disappears without a manual cleanup ticket.

The Microsoft Entra PIM model exists so you can require evidence before granting temporary privilege, and it makes the access decision explicit instead of implicit.

The activation process can require several controls before a role becomes active, and that sequence is where governance policies get enforced:

  • Time-bounding: The role automatically expires after a set period (e.g., 2 hours).
  • MFA: The user must perform multi-factor authentication to activate.
  • Approval: A manager or security team member must approve the activation request.
  • Ticketing: The user must provide a valid Jira/ServiceNow ticket number as justification.

Stop and think: An engineer is eligible for the Subscription Owner role via PIM. They activate the role, which requires approval. After approval, they try to delete a resource group but get an authorization error. 15 minutes later, the deletion succeeds. Why? Answer: Azure RBAC role assignments can take up to 10 minutes to propagate across the globally distributed authorization system. PIM works by dynamically creating a temporary role assignment when activated, so the standard propagation delay applies. The user must simply wait a few minutes after activation for the permissions to take effect.

Over time, users accumulate permissions they no longer need—often from changing teams, temporary project assignments, or evolving org structures. Access Reviews automate cleanup by giving owners and reviewers a periodic checkpoint, which is far more reliable than relying on people to remember every stale permission. You can configure this process to ask users, managers, or resource owners on a cadence (for example, quarterly) whether specific access is still required. If the reviewer says “no” or fails to respond within the timeframe, Entra ID can automatically revoke access, which directly supports periodic governance evidence and least-privilege review processes. Deploy access reviews with explicit reviewers and auto-removal settings documented so audit evidence is reproducible.

Access reviews complement PIM: PIM limits how long elevated access lasts during an incident window; access reviews prove whether standing or eligible assignments should exist at all. For this reason, Access Reviews are most effective when they are tied to clear ownership and realistic review cycles.

Entra ID Licensing: Free, P1, P2, and What Requires a Paid Seat

Section titled “Entra ID Licensing: Free, P1, P2, and What Requires a Paid Seat”

Microsoft Entra ID Free is included with Azure and Microsoft 365 subscriptions and covers basic directory features, security defaults with MFA for all users (with Authenticator as the primary method in defaults), and built-in Azure RBAC role usage. Entra ID P1 adds Conditional Access, dynamic groups, self-service password reset with writeback, Entra Connect Health, and custom directory roles (each user with a custom role assignment needs P1). Entra ID P2 adds Privileged Identity Management, Entra ID Protection (risk-based policies), and advanced access review capabilities bundled with governance scenarios.

CapabilityFreeP1P2
Security defaults / basic MFAYesYesYes
Conditional Access policiesNoYesYes
Dynamic group membershipNoYesYes
Custom Entra directory rolesNoYes (licensed assignees)Yes
Privileged Identity ManagementNoNoYes
Risk-based Conditional AccessNoNoYes (via ID Protection)
Managed identities for Azure resourcesYes (no extra license)YesYes

Microsoft 365 E3 includes P1; Microsoft 365 E5 and EMS E5 include P2. Small-business Microsoft 365 Business Premium also bundles Conditional Access—check your exact SKU before buying standalone P1 seats.


PatternWhen to useWhy it worksScaling note
Group-based RBACAny team larger than a handful of engineersRole assignments on groups survive org churn; audit shows group membership changesUse dynamic groups for department-based access; avoid nested group explosions
User-assigned managed identity shared across servicesMultiple Azure resources need the same downstream permissionsOne principalId, one set of Key Vault or SQL role assignmentsPre-create identity in Terraform/Bicep before attaching to App Service, Functions, ACI
Workload identity federation for CI/CDGitHub Actions, GitLab, or non-Azure Kubernetes calling Azure APIsNo rotating client secrets in pipeline variablesOne federated credential per repo/environment subject; review issuer/subject in PRs
Conditional Access baseline + report-onlyNew tenant hardeningMFA for admins and block legacy auth stop most commodity attacksExpand policies gradually; exclude break-glass accounts explicitly
PIM eligible for Owner/Global AdminProduction subscriptions and tenant administrationStanding privilege window shrinks to activation durationLicense every eligible user, approver, and reviewer (P2 or Governance)
Anti-patternWhat goes wrongWhy teams fall into itBetter alternative
Subscription Owner for all engineersOne compromised laptop deletes entire environmentsSpeed during early cloud adoptionContributor or custom roles at RG scope; PIM Owner when needed
Client secret in pipeline variablesSecret leaks via logs, forks, or retired maintainersTutorials default to AZURE_CLIENT_SECRETFederated credential + OIDC login action
System-assigned MI per microservice when permissions are identicalN identities, N role assignments, N audit trailsEach tutorial enables [system] locallyOne user-assigned MI attached to all components
Global Admin for day-to-day Azure portal workDirectory and resource planes blurred; over-broad accessConfusion between Entra roles and RBACRBAC Reader/Contributor at scoped resources; PIM for elevation
Conditional Access without break-glass planLockout during MFA or location misconfiguration”Secure everything” rushTwo cloud-only emergency accounts, excluded from CA, monitored
Ignoring deny assignments during RCA”Contributor” still fails mysteriouslyDeny is invisible in simple IAM screenshotsCheck Deny assignments tab; review deployment stack deny settings

Decision Framework: Workload Identity Choice

Section titled “Decision Framework: Workload Identity Choice”

Use this matrix when onboarding a new workload that needs Entra-authenticated access to Azure APIs. The goal is no long-lived secrets and narrow RBAC scope.

Workload contextRecommended identityWhyAvoid
Single Azure VM, App Service, or FunctionSystem-assigned managed identityLifecycle tied to resource; zero secret managementUser-assigned unless you will share permissions
Multiple Azure resources, same permissionsUser-assigned managed identityOne principal, many attachmentsDuplicate system-assigned identities
AKS pod accessing Azure APIsUser-assigned MI + workload identity federationOIDC trust to Kubernetes service accountMounting service principal secrets into cluster
GitHub Actions deploying to AzureApp registration + federated credential (OIDC)Branch/repo-scoped trust, no secret rotationClient secret in GitHub Secrets
Third-party SaaS outside AzureApp registration + certificate or federation if supportedExplicit consent and scoped API permissionsContributor role on subscription for the vendor
Legacy app that cannot use OIDC/MIService principal + short-lived certificateCertificates rotatable via Key VaultMulti-year client secrets in config files
flowchart TD
A[Workload needs Azure API access] --> B{Runs on Azure with IMDS?}
B -->|Yes, single resource| C[System-assigned managed identity]
B -->|Yes, multiple resources same perms| D[User-assigned managed identity]
B -->|No| E{External OIDC issuer available?}
E -->|Yes: GitHub/K8s/GCP/AWS| F[Federated credential on MI or app registration]
E -->|No| G{Can use certificate auth?}
G -->|Yes| H[App registration + cert from Key Vault]
G -->|No| I[Service principal secret - last resort, short TTL]
C --> J[Assign RBAC at narrowest scope]
D --> J
F --> J
H --> J
I --> J

Cost Lens: Entra Licensing and Over-Provisioning

Section titled “Cost Lens: Entra Licensing and Over-Provisioning”

Identity licensing is a per-user monthly line item that is easy to overspend because it is invisible next to VM and storage bills. Managed identities for Azure resources incur no additional Entra license charge—you pay for the Azure resources themselves, not per identity. The cost driver is human and governance seats: Conditional Access, dynamic groups, PIM activations, and access reviews each map to P1, P2, or Entra ID Governance licensing rules.

Free tier covers directory basics and security defaults. If you only need cloud-only users, standard groups, and built-in RBAC on subscriptions, you may never buy P1—until you need Conditional Access for compliance, at which point every user in scope of those policies needs P1 (or a bundle like Microsoft 365 E3 that includes P1).

P1 is the enterprise baseline for policy-driven sign-in. Budget P1 (or E3) for employees subject to Conditional Access and for administrators who use custom Entra directory roles. A common over-licensing mistake is assigning P1 to every contractor when only employees hitting CA policies need it—use separate guest policies or scoped assignments.

P2 is required for PIM eligible/active role workflows, risk-based Conditional Access, and Entra ID Protection reports. Microsoft documents that every user who is eligible for PIM, can approve activations, or participates in access reviews needs a P2 or Entra ID Governance license. A team of five eligible Owners plus three approvers plus quarterly reviewers can require more licensed identities than expected because approvers and reviewers count too.

Cost spikes to watch:

  • Buying standalone P2 when Microsoft 365 E5 already includes it for the same users (duplicate spend).
  • Leaving PIM eligible assignments on vendors or contractors who no longer need them—licenses still attach to identities in review scope.
  • Enabling advanced access review features without Entra ID Governance planning—some scenarios bill per reviewer and per reviewed user.
  • Using service principals with secrets instead of federation—no license savings, but incident cost dominates when secrets leak.

Knobs that reduce cost without weakening security: use group-based licensing in Microsoft 365 admin center; align CA policies to groups rather than “all users” when possible; prefer managed identities (no license) over human service accounts; use PIM time bounds so fewer people need standing Owner; right-size P2 to identities in governance workflows only.

Hypothetical scenario: a 200-person company buys 200 P2 licenses “for security,” but only 15 administrators use PIM and 30 employees are in Conditional Access scope. A disciplined model might be 30 P1-equivalent (E3) plus 20 P2 for admins and reviewers—saving substantial monthly cost while keeping controls on the identities that actually touch privileged paths.

Licensing hygiene checklist (quarterly): reconcile Microsoft 365 admin center group-based licensing with Entra group membership; remove P2 from users without eligible PIM roles; confirm contractors are guests with scoped access reviews; verify GitHub federated credentials still match active repos; audit app registrations with secrets older than 90 days.

Tenants without P1 historically relied on security defaults—tenant-wide baseline requiring MFA for admins and users, and blocking legacy authentication. Security defaults are mutually exclusive with Conditional Access in many tenants—you choose a baseline path. Moving to Conditional Access means buying P1 (or bundled E3/Business Premium) and migrating policies deliberately. Do not disable security defaults until equivalent CA policies exist, or you temporarily weaken sign-in protections.


  1. Microsoft Entra ID was renamed from Azure Active Directory in July 2023, but the CLI commands still use az ad (not az entra), the PowerShell module is still AzureAD, and many API endpoints still reference azure-active-directory. This naming mismatch is likely to remain because Microsoft keeps existing tooling and API surfaces stable for backward compatibility.

  2. Microsoft Entra tenants are subject to directory object quotas, and applications and service principals count toward those tenant-wide limits. In large environments, service principals can accumulate quickly because first-party apps, SaaS integrations, and internal tools all create directory objects.

  3. On Azure compute resources that use managed identities, client libraries can obtain tokens from a local metadata endpoint that is reachable only from within the resource. This design avoids exposing long-lived secrets over the public internet.

  4. New Azure RBAC role assignments can take several minutes to take effect. This delay catches people during deployments---you assign a role and immediately try to use it, and it fails. Build in a wait or retry mechanism when programmatically assigning roles that need to work right away.


MistakeWhy It HappensHow to Fix It
Using client secrets for service principals in production workloads on AzureIt’s the “easy” way shown in many tutorialsUse Managed Identities for Azure-hosted workloads. Use OIDC federated credentials for external CI/CD.
Granting Owner at subscription scope “just to get things working”Contributor seems insufficient during initial setupIdentify the specific actions needed and use a narrower built-in role or create a custom role.
Confusing Entra ID roles with Azure RBAC rolesThe naming is genuinely confusing, and both appear in the portalRemember: Entra ID roles = directory operations. RBAC roles = resource operations. Check which scope you need.
Not using groups for role assignmentsIt seems faster to assign roles directly to usersPrefer assigning RBAC roles to groups, then manage group membership. This scales and is auditable.
Leaving orphaned service principal secrets activeTeams create secrets for one-off tasks and forget themSet short expiration dates. Audit with az ad app credential list. Use workload identity federation.
Setting system-assigned identity when user-assigned is more appropriateSystem-assigned is the default in most tutorialsIf multiple resources need the same identity/permissions, use user-assigned. It simplifies role management.
Not restricting the AssignableScopes of custom rolesDevelopers copy examples that use subscription scopeScope custom roles to the narrowest possible scope (resource group when possible).
Skipping Conditional Access for break-glass accountsEmergency accounts seem like they should bypass everythingExclude emergency access accounts from routine Conditional Access policies to avoid lockout, and monitor them closely with alerts and regular review.

1. Your company acquired a startup. You need to enforce your corporate Azure Policies on the startup's cloud environment, but their cloud costs must be billed completely separately from your corporate resources. How should you architect the hierarchy?

Create a new Management Group for the startup under your Root Management Group. Place the startup’s Subscription under this new Management Group. You can apply your corporate Azure Policies at the Management Group level, which will inherit down to their Subscription. Because they remain in their own distinct Subscription, their billing is isolated and can be tied to a different payment method or invoice, while still sharing the same Entra ID tenant and governance boundaries.

2. An external auditor needs to review your Entra ID Conditional Access policies and also verify the Network Security Group (NSG) rules on your production Azure Virtual Networks. What is the minimum combination of roles you must assign them?

You must assign them Security Reader (an Entra ID directory role) so they can view the Conditional Access policies in the tenant. You must also assign them Reader (an Azure RBAC resource role) at the Subscription or Resource Group scope so they can view the NSG configurations. This highlights the separation between directory management and resource management. No single read-only role spans both the directory and resource planes by default, requiring discrete assignments.

3. A microservices application consists of an Azure App Service, an Azure Function, and an Azure Container Instance. All three components need to authenticate to the same Azure SQL Database. How should you architect the identity solution to minimize maintenance overhead?

You should create a single User-Assigned Managed Identity. You grant this user-assigned identity the required RBAC access to the Azure SQL Database. Then, you attach this single identity to the App Service, the Azure Function, and the Container Instance. If you used System-Assigned Managed Identities, you would have three separate identities to manage and three separate role assignments to maintain in the SQL database, increasing maintenance overhead.

4. You assigned the 'Contributor' role to a data scientist on a Resource Group containing a Storage Account. They report they can create new storage containers but get an 'Access Denied' error when trying to upload a CSV file into a container. What is causing this, and how do you fix it?

The built-in ‘Contributor’ role has broad Actions permissions, which allow management plane operations like creating containers or deleting the storage account. However, it has zero DataActions permissions, which are required for data plane operations like reading or writing the actual blobs and files. To fix this, you must assign the data scientist a data-plane role, such as Storage Blob Data Contributor. This role contains the specific DataActions required to upload and manipulate files within the storage containers.

5. You are designing the RBAC model for a new platform engineering team. A junior engineer suggests creating a custom role and assigning it directly to each of the 12 team members. Why should you reject this proposal, and what is the standard approach?

Assigning roles directly to individual users does not scale and creates a nightmare for lifecycle management. When someone leaves the team or changes roles, you must manually track down and remove their individual role assignments across all resources. Over time, this leads to permission bloat and orphaned access rights. Instead, you should create an Entra ID Security Group (e.g., ‘Platform Engineers’), assign the RBAC role to the group, and manage access solely by adding or removing users from that group.

6. You accidentally deleted a Virtual Machine that had a System-Assigned Managed Identity with access to a production Key Vault. You quickly restore the VM from a backup with the exact same name. However, the application on the VM is now failing to read from the Key Vault. Why?

A System-Assigned Managed Identity is inextricably tied to the specific lifecycle of the Azure resource it was created on. When you deleted the original VM, its managed identity (and all associated role assignments) was permanently destroyed by Azure. When you restored the VM, Azure generated a brand new System-Assigned Managed Identity with a completely different Principal ID. You must recreate the RBAC role assignments on the Key Vault for this new identity before the application can authenticate.

7. Your security team wants GitHub Actions in `repo:contoso/deploy:environment:production` to deploy to Azure without storing `AZURE_CLIENT_SECRET`. The app registration already exists. What Entra feature do you configure, and what three fields must match the token GitHub sends?

Configure a federated identity credential on the app registration (or on a user-assigned managed identity if the pipeline assumes that identity). The credential’s issuer must match GitHub’s OIDC issuer (https://token.actions.githubusercontent.com), the subject must match the workflow claim (for example repo:contoso/deploy:environment:production), and the audience must match what the login action expects (commonly api://AzureADTokenExchange). Microsoft identity platform rejects the exchange if any value differs by even case from the external JWT. This is workload identity federation: external OIDC token in, Entra access token out, with no client secret in GitHub.

8. A contractor still appears as *eligible* for User Administrator in PIM, but your organization let their Microsoft 365 license expire last month. They attempt to activate the role and the portal blocks them. Which licensing lesson applies, and what should operations do?

PIM requires Microsoft Entra ID P2 or Entra ID Governance licenses for users with eligible assignments, approvers, and reviewers—not merely an Azure subscription. Expired or missing P2/Governance licensing disables activation even if the eligible assignment row still exists. Operations should remove stale eligible assignments during offboarding, reconcile licenses in the Microsoft 365 admin center, and run access reviews so contractors do not remain in privileged eligibility after employment ends. This is why identity governance is both a security process and a licensing compliance process.


Hands-On Exercise: Custom RBAC Role + Managed Identity on a VM

Section titled “Hands-On Exercise: Custom RBAC Role + Managed Identity on a VM”

In this exercise, you will create a custom RBAC role, set up a VM with a Managed Identity, and verify that the identity can only perform the actions allowed by the custom role. The lab stitches together three ideas from this module: custom roles narrow management and data actions, system-assigned managed identities remove secrets from the VM, and scope-limited assignments show least privilege in practice. You will see propagation delay in Task 5—build the wait into your scripts in production.

The exercise intentionally uses DataActions with NotDataActions so listing and reading blobs succeed while delete fails. That mirrors real application needs: telemetry agents that read inputs but must not wipe archives. If you only granted Contributor, the VM could delete containers even when blob delete should be forbidden—another reminder that management-plane roles do not imply data-plane rights.

Prerequisites: Azure CLI installed and authenticated (az login), a resource group to work in, and SSH access to a temporary VM for identity verification. Use a sandbox subscription; the VM is a small Standard_B1s burstable size. Delete the resource group in Cleanup to avoid ongoing compute charges.

Task 1: Create a Resource Group for the Exercise

Section titled “Task 1: Create a Resource Group for the Exercise”
Terminal window
# Set variables
RG_NAME="kubedojo-identity-lab"
LOCATION="eastus2"
# Create the resource group
az group create --name "$RG_NAME" --location "$LOCATION"
Verify Task 1
Terminal window
az group show --name "$RG_NAME" --query '{Name:name, Location:location, State:properties.provisioningState}' -o table

You should see the resource group with Succeeded state.

Task 2: Create a Custom RBAC Role (Storage Blob Lister)

Section titled “Task 2: Create a Custom RBAC Role (Storage Blob Lister)”

Create a custom role that can list storage accounts and list and read blobs---but cannot write or delete blob contents.

Terminal window
# Create the role definition file
cat > /tmp/blob-lister-role.json << 'EOF'
{
"Name": "Storage Blob Lister",
"Description": "Can list storage accounts and enumerate and read blobs but cannot write or delete blob content",
"Actions": [
"Microsoft.Storage/storageAccounts/read",
"Microsoft.Resources/subscriptions/resourceGroups/read"
],
"DataActions": [
"Microsoft.Storage/storageAccounts/blobServices/containers/read",
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"
],
"NotDataActions": [
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write",
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete"
],
"AssignableScopes": [
"/subscriptions/YOUR_SUB_ID"
]
}
EOF
# Replace with your subscription ID
SUB_ID=$(az account show --query id -o tsv)
sed -i '' "s|YOUR_SUB_ID|$SUB_ID|g" /tmp/blob-lister-role.json 2>/dev/null || \
sed -i "s|YOUR_SUB_ID|$SUB_ID|g" /tmp/blob-lister-role.json
# Create the custom role
az role definition create --role-definition @/tmp/blob-lister-role.json
Verify Task 2
Terminal window
az role definition list --name "Storage Blob Lister" \
--query '[0].{Name:roleName, Type:roleType, Actions:permissions[0].actions}' -o json

You should see the custom role with CustomRole type and the actions you defined.

Task 3: Create a Storage Account with a Test Blob

Section titled “Task 3: Create a Storage Account with a Test Blob”
Terminal window
# Create storage account (name must be globally unique)
STORAGE_NAME="kubedojolab$(openssl rand -hex 4)"
az storage account create \
--name "$STORAGE_NAME" \
--resource-group "$RG_NAME" \
--location "$LOCATION" \
--sku Standard_LRS
STORAGE_ID=$(az storage account show --name "$STORAGE_NAME" --resource-group "$RG_NAME" --query id -o tsv)
# Owner/Contributor grant management-plane rights but NOT blob data access — assign a data role.
ME=$(az ad signed-in-user show --query id -o tsv)
az role assignment create --assignee "$ME" --role "Storage Blob Data Contributor" \
--scope "$STORAGE_ID"
# wait ~30s for the role assignment to propagate before the data-plane ops below
sleep 30
# Create a container
az storage container create \
--name "testcontainer" \
--account-name "$STORAGE_NAME" \
--auth-mode login
# Upload a test blob
echo "Hello from KubeDojo identity lab" > /tmp/testfile.txt
az storage blob upload \
--container-name "testcontainer" \
--file /tmp/testfile.txt \
--name "testfile.txt" \
--account-name "$STORAGE_NAME" \
--auth-mode login
Verify Task 3
Terminal window
az storage blob list --container-name "testcontainer" \
--account-name "$STORAGE_NAME" --auth-mode login \
--query '[].name' -o tsv

You should see testfile.txt.

Task 4: Create a VM with a System-Assigned Managed Identity

Section titled “Task 4: Create a VM with a System-Assigned Managed Identity”
Terminal window
az vm create \
--resource-group "$RG_NAME" \
--name "identity-lab-vm" \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--generate-ssh-keys \
--assign-identity '[system]' \
--output table
Verify Task 4
Terminal window
az vm identity show --resource-group "$RG_NAME" --name "identity-lab-vm" \
--query '{Type:type, PrincipalId:principalId}' -o table

You should see SystemAssigned type and a principal ID (a GUID).

Task 5: Assign the Custom Role to the VM’s Managed Identity

Section titled “Task 5: Assign the Custom Role to the VM’s Managed Identity”
Terminal window
# Get the VM's managed identity principal ID
VM_PRINCIPAL_ID=$(az vm identity show \
--resource-group "$RG_NAME" \
--name "identity-lab-vm" \
--query principalId -o tsv)
# Assign the custom role scoped to the storage account
STORAGE_ID=$(az storage account show --name "$STORAGE_NAME" --resource-group "$RG_NAME" --query id -o tsv)
az role assignment create \
--assignee "$VM_PRINCIPAL_ID" \
--role "Storage Blob Lister" \
--scope "$STORAGE_ID"
# Wait for propagation
echo "Waiting 60 seconds for role assignment propagation..."
sleep 60
Verify Task 5
Terminal window
az role assignment list \
--assignee "$VM_PRINCIPAL_ID" \
--scope "$STORAGE_ID" \
--query '[].{Role:roleDefinitionName, Scope:scope}' -o table

You should see Storage Blob Lister assigned at the storage account scope.

Task 6: Test the Managed Identity from Inside the VM

Section titled “Task 6: Test the Managed Identity from Inside the VM”

SSH into the VM and verify that the Managed Identity can list blobs but cannot delete them by running each command exactly as shown and observing the expected authorization behavior.

Terminal window
# SSH into the VM
VM_IP=$(az vm show -g "$RG_NAME" -n "identity-lab-vm" -d --query publicIpAddress -o tsv)
ssh -o StrictHostKeyChecking=no azureuser@"$VM_IP"
# Inside the VM, install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Login using the VM's managed identity
az login --identity
# This should SUCCEED: list blobs
az storage blob list --container-name "testcontainer" \
--account-name "$STORAGE_NAME" --auth-mode login \
--query '[].name' -o tsv
# This should SUCCEED: read blob content
az storage blob download --container-name "testcontainer" \
--name "testfile.txt" --file /tmp/downloaded.txt \
--account-name "$STORAGE_NAME" --auth-mode login
cat /tmp/downloaded.txt
# This should FAIL: delete blob (NotDataActions blocks this)
az storage blob delete --container-name "testcontainer" \
--name "testfile.txt" \
--account-name "$STORAGE_NAME" --auth-mode login
# Expected: AuthorizationPermissionMismatch error
Verify Task 6

If the list and read operations succeed but the delete operation returns an authorization error, you have successfully configured a custom RBAC role with a Managed Identity. The identity can enumerate and read blobs but cannot modify or delete them.

Terminal window
# Delete everything
az group delete --name "$RG_NAME" --yes --no-wait
az role definition delete --name "Storage Blob Lister"

After cleanup, confirm the custom role no longer appears in az role definition list --custom-role-only true. If the role definition delete fails because assignments still exist, remove assignments first with az role assignment delete. Labs that skip cleanup leave custom roles and storage accounts billing quietly in sandbox subscriptions. Document your principal IDs during the lab—they help when comparing audit logs and exported Azure RBAC role assignment lists later.

  • Custom RBAC role “Storage Blob Lister” created with specific actions and data actions
  • Storage account created with a test blob in a container
  • VM created with system-assigned Managed Identity enabled
  • Custom role assigned to the VM’s Managed Identity at storage account scope
  • From inside the VM, Managed Identity can list and read blobs
  • From inside the VM, Managed Identity cannot delete blobs (authorization error)

Module 3.2: Virtual Networks (VNet) --- Learn how Azure networking works, from VNets and subnets to NSGs, peering, and the hub-and-spoke architecture that every enterprise uses.