A managed identity removes the stored secret, but whatever it is allowed to do becomes available to anyone who can run code on the resource it is attached to. That makes over-privileged managed identities one of the quietest paths from a compromised web app or virtual machine to a whole subscription or tenant. This guide is a practical procedure to find them: enumerate every system-assigned and user-assigned identity, collect both its Azure RBAC role assignments and its Microsoft Graph application permissions, flag the dangerous combinations, and reduce them without breaking workloads. It is for cloud platform, identity and security teams. For the concepts and the design choices, read the Azure Managed Identities Security Guide first.
Key takeaways
- Check two permission planes. A managed identity can hold Azure RBAC roles on resources and, separately, Microsoft Graph app roles (application permissions) in the tenant. Most reviews look only at the first.
- Privileged roles are a short list. Microsoft classes Owner, Contributor, Role Based Access Control Administrator, User Access Administrator and Reservations Administrator as privileged administrator roles; any of these on a managed identity needs a written justification.
- Scope matters as much as role. Contributor on one resource group is a different risk from Contributor on a subscription; Microsoft's own example of excess privilege is a managed identity made Contributor on a subscription.
- Who can use the identity is part of its privilege. Anyone who can run code on the host resource, or who holds the assign action on a user-assigned identity, inherits everything the identity can do.
- Deleting an identity leaves its role assignments behind. They appear as "Identity not found" with an
ObjectTypeofUnknownand should be cleaned up.
When you need this
- Periodic entitlement review of workload identities, at least quarterly.
- After a compromise of an app or VM: the first question is what its identity could reach. In the Storm-2949 Azure attack, the attacker used Run Command to request a VM's managed identity token from the Instance Metadata Service and tried to read the production Key Vault; it failed only because that identity had not been granted access.
- Before a cloud security audit or a Defender for Cloud remediation sprint.
- When user-assigned identities have been shared across many resources and nobody is sure which still need them.
Before you start
- Get read access across scopes: Reader at the root management group (or each subscription) to list role assignments, and a Microsoft Graph permission sufficient to read service principals and their app role assignments.
- Agree the definition of over-privileged using the table below, so findings are not argued one by one.
- Find owners: resource tags or deployment pipelines usually show which team owns each host resource.
- Plan a change path: role changes should go through the same infrastructure-as-code pipeline that created them, or they will be re-applied on the next deployment.
| Finding | Why it is dangerous | Severity |
|---|---|---|
| Owner, User Access Administrator or Role Based Access Control Administrator | Can assign roles, so the identity can grant itself or others anything in scope | Critical |
| Contributor at subscription or management group scope | Full management of all resources in scope | High |
Key Vault Contributor (or any Microsoft.KeyVault/vaults/write) on vaults using access policies | Can edit the vault's access policy and grant itself secret access, as in the Azure Key Vault Contributor escalation | High |
| Write-capable Microsoft Graph app roles (for example directory, application or role management write permissions) | Tenant-wide control independent of any Azure subscription | Critical |
| Managed Identity Operator held by broad groups | Includes userAssignedIdentities/*/assign/action, so holders can attach the identity to their own resources | High |
Role assignments with ObjectType Unknown | Orphans left after identity deletion; clutter that hides real findings and consumes assignment limits | Low |
Step-by-step
Steps 1 to 5 are read-only. Step 6 removes access and is destructive for any workload that still depends on it.
- Enumerate managed identities. In Microsoft Graph, list service principals and select
servicePrincipalType; Microsoft documents its values asApplication,ManagedIdentityandLegacy. Keep theManagedIdentityentries: this captures both system-assigned and user-assigned identities across the tenant. For user-assigned identities with their resource groups,az identity listgives the Azure-side view. - Collect Azure RBAC assignments. For each identity's object (principal) ID, run
az role assignment list --assignee {objectId} --all --include-inherited.--allcovers the current subscription and below, and--include-inheritedadds assignments at parent scopes; add--include-groupsif identities are placed in groups. For a whole estate, query the Azure Resource Graphauthorizationresourcestable, which holdsmicrosoft.authorization/roleassignmentsandroledefinitions, and join on principal ID. - Collect Microsoft Graph application permissions. For each identity, call
GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/appRoleAssignments. Microsoft notes that app roles assigned to service principals are application permissions. Resolve eachappRoleIdagainst the resource service principal (usually Microsoft Graph) to get the permission name. - Map who can use each identity. For system-assigned identities, list who can run code on the host (Contributor or specific write roles on the VM, App Service, Function or Logic App, and Run Command rights). For user-assigned identities, list every resource it is attached to and every principal with Managed Identity Operator or the assign action on it.
- Check actual use. In the Entra admin centre, the managed identity sign-in log shows sign-ins grouped by managed identity and resource. An identity with privileged roles and no sign-ins in the review period is a candidate for removal. Resource activity logs show which operations it actually performed, which tells you the narrower role to use.
- Reduce (destructive).
- Swap the role: assign the narrow built-in or custom role at the smallest scope first, test, then remove the broad one with
az role assignment delete --assignee "{objectId}" --role "Contributor" --scope "{scope}". - Remove a Graph permission:
DELETE https://graph.microsoft.com/v1.0/servicePrincipals/{id}/appRoleAssignments/{appRoleAssignment-id}. - Clean orphans: Microsoft documents
Get-AzRoleAssignment | Where-Object {$_.ObjectType -eq "Unknown"} | Remove-AzRoleAssignment. Run it first without the final pipe to review what will be removed. - Delete unused user-assigned identities manually: Microsoft states they are not removed when the resources using them are deleted.
- Swap the role: assign the narrow built-in or custom role at the smallest scope first, test, then remove the broad one with
| Plane | Read-only command | Change command (destructive) | Notes |
|---|---|---|---|
| Inventory (Entra) | GET /v1.0/servicePrincipals selecting servicePrincipalType | None | Covers system-assigned and user-assigned identities in one list |
| Inventory (Azure) | az identity list | Delete the user-assigned identity when unused | User-assigned identities outlive the resources that used them |
| Azure RBAC, one identity | az role assignment list --assignee {objectId} --all --include-inherited | az role assignment delete --assignee ... --role ... --scope ... | Without --all only the current subscription is shown |
| Azure RBAC, whole estate | Resource Graph, authorizationresources table | Through infrastructure as code | Join role assignments to role definitions to get role names |
| Microsoft Graph | GET /servicePrincipals/{id}/appRoleAssignments | DELETE /servicePrincipals/{id}/appRoleAssignments/{id} | Application permissions apply tenant-wide |
| Orphans | Get-AzRoleAssignment | Where-Object {$_.ObjectType -eq "Unknown"} | Pipe to Remove-AzRoleAssignment | Shown as "Identity not found" in the portal |
Work in this order for a large estate: run the estate-wide queries first to rank identities by the severity table, then use the per-identity commands only on the top of that list. In a large tenant, the identities with privileged administrator roles or Graph write permissions are normally a small subset, and they carry most of the risk. Small organisations with a handful of subscriptions can run the per-identity commands directly and finish the review in an afternoon.
Verify it worked
- Re-run steps 2 and 3 for each changed identity and compare with the baseline: no privileged administrator roles without an approved exception, no broad scopes, no write-capable Graph permissions without approval.
- Watch the workload: check application logs and the managed identity sign-in log for authorisation failures in the days after the change.
- Confirm infrastructure as code matches: the next pipeline run must not re-create the removed assignment.
Rollback and common errors
- Rollback: re-create the previous assignment from your baseline export (role, scope, principal ID) through the pipeline. Keep it time-boxed and raise a ticket to find the narrower role.
- Removing before adding: always add the narrow role and test before deleting the broad one.
- Wrong scope on delete: Microsoft notes the error "The provided information does not map to a role assignment" when scope is missing; supply
--scopeor--resource-group. - Permission changes that seem not to apply: Microsoft warns that group membership and role changes for managed identities can take time to appear in tokens because of caching, so test after the delay rather than reverting immediately.
- Turning the identity off as a shortcut: disabling a system-assigned identity deletes it, but Microsoft states its role assignments are not deleted with it. You lose the ability to trace them by name and gain orphans to clean up. Remove the assignments first, then the identity, and record both in the change ticket.
- Missing the Graph plane: an identity with only Reader in Azure can still hold tenant-wide Graph write permissions.
Hardening afterwards
- Prefer system-assigned identities where permissions should die with the resource; use user-assigned identities deliberately and limit who can assign them.
- Restrict code execution paths on hosts with privileged identities: Run Command, VM extensions, deployment slots and Kudu access are all ways to borrow the identity.
- Move Key Vaults to the Azure RBAC permission model so vault management roles cannot grant data access.
- Alert on change: new privileged role assignments to service principals of type ManagedIdentity, new Graph app role assignments, and role assignments at subscription scope or above. See the Entra ID Sign-In and Audit Log Detection Guide.
- Use Azure Policy or pipeline checks to block privileged administrator roles for workload identities unless an exception tag is present.
Where identity fits: managed identities are the cleanest form of non-human identity in Azure because there is no secret to leak, which is exactly why teams stop reviewing them. Track each one with an owner, a host resource, a purpose and a review date, like any other machine identity, and include them in the same certification campaigns as service principals. See the Azure RBAC and Service Principals Guide.
Measure it: managed identities holding privileged administrator roles, identities with subscription-scope or wider assignments, identities with write-capable Graph app roles, orphaned (Unknown) assignments, user-assigned identities with no attached resource, and percentage of identities reviewed in the last 90 days.
| Framework | Reference | Relevance |
|---|---|---|
| MITRE ATT&CK | T1078.004 Cloud Accounts; T1098 Account Manipulation | Abuse of a workload identity's access; role grants for persistence |
| NIST SP 800-53 Rev 5 | AC-6; AC-2; CM-7 | Least privilege, account review, least functionality |
| NIST CSF 2.0 | PR.AA-05; PR.AA-01 | Reviewed permissions; managed identities |
Practitioner checklist
- List every service principal of type ManagedIdentity in the tenant
- Collect Azure RBAC assignments with
--all --include-inheritedfor each identity - Collect Microsoft Graph app role assignments for each identity
- Flag privileged administrator roles, subscription-scope Contributor and write-capable Graph permissions
- Map who can run code on each host resource or assign each user-assigned identity
- Add the narrow role and test before removing the broad one
- Clean up orphaned role assignments and delete unused user-assigned identities
Standards and references
- Microsoft Learn: Managed identity best practice recommendations
- Microsoft Learn: List Azure role assignments using Azure CLI
- Microsoft Learn: Steps to assign an Azure role (privileged administrator roles)
- Microsoft Learn: Best practices for Azure RBAC
- Microsoft Graph: List appRoleAssignments granted to a service principal
- Microsoft Learn: Managed identity sign-ins
- MITRE ATT&CK: T1078.004 Cloud Accounts
Related NHI Mgmt Group resources: Azure Managed Identities Security Guide · Azure RBAC and Service Principals Guide · How to find and remove stale service principals in Entra ID · Least Privilege Techniques Guide