Summary
Maybe you already saw an entry like the following in the blade Access control (IAM)
blade in the Azure Portal:
So basically what Identity not found
means is that there is still a role assignment but the associated role assignment as been removed.
You can test this with the following script:
$group = New-AzResourceGroup -Name rg-stale-test -Location westeurope
$principal = New-AzAdServicePrincipal -DisplayName 'TEST'
New-AzRoleAssignment -ObjectId $principal.Id -Scope $group.ResourceId -RoleDefinitionName 'Reader'
Remove-AzADServicePrincipal -Id $principal.Id
You might wait a few seconds but after this you’ll get an Identity not found
(stale) entry when you go to your resource group in the Azure Portal and click on Access control (IAM)
and then on the Role Assignments
tab.
Over time a lot of these entries can occur in your Azure tenant.
It is interesting to see Microsoft doing this in Azure again! Some readers may find this behavior familiar. It is in principle the same as looking into the security properties of a file or folder in Windows Explorer and seeing those
S-1-...
entries. Those are permissions in the Windows file system pointing to principals that no longer exist. I think this is odd especially in Azure. The entries serve no purpose AFAIK and should be removed together with the associated identity. At least there should be a warning when you remove the identity.
PowerShell Oneliner
Removing all stale assignments for a known resource is pretty easy using this PowerShell line:
Get-AzRoleAssignment -Scope $group.ResourceId | `
Where-Object { $_.ObjectType -eq 'Unknown' } | `
Remove-AzRoleAssignment
The scope points to the id of the resource the assignment was applied to. In my sample I can use $group.ResourceId
because in listing 1 I stored the output of New-AzResourceGroup
in a variable and can access its ResourceId
now.
Automation
So knowing this it is clear that we should run this over all our resources and remove those entries automatically. To achieve this I came up with a script that utilizes Azure Resource Graph to collect all resource containers (subscriptions, resource groups, management groups) in a tenant and then iterates over them trying to remove all stale assignments.
I say “trying” because some of the containers may be secured by a no-delete resource lock. So the script checks that and informs me whenever it is not able to remove the role assignment.
In order to execute this script you need to install the module Az.ResourceGraph
which is not part of the module collection Az
in PowerShell.
Here is my script:
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$TenantId
)
$containers = Search-AzGraph -Query "ResourceContainers | where tenantId == '$TenantId'"
$containerCount = $containers.Count
$current = 0
$removed = 0
$notRemoved = 0
$notRemovedIds = New-Object System.Collections.Generic.List[string]
foreach($container in $containers) {
$current = $current + 1
Write-Host "Removing stale role assignments from [$($container.name)] ($current of $containerCount)..."
$stales = Get-AzRoleAssignment -Scope $container.Id | Where-Object { $_.ObjectType -eq 'Unknown' }
$amount = $stales.Count
if ($amount -gt 0) {
$deleteLocks = Get-AzResourceLock -Scope $container.Id | Where-Object { $_.Properties.level -eq 'CanNotDelete' }
if ($deleteLocks.Count -gt 0) {
Write-Host "Cannot remove $($amount) stale role assignments for [$($container.name)] because of existing no-delete-lock." -ForegroundColor Yellow
$notRemoved = $notRemoved + $amount
$notRemovedIds.Add($container.Id)
continue
}
$stales | Remove-AzRoleAssignment
$removed = $removed + $amount
Write-Host "Removed $($amount)" -ForegroundColor Green
continue
}
Write-Host "Done"
}
Write-Host "Finished. Removed $removed stale role assignemnts." -ForegroundColor Green
if ($notRemovedIds.Count -gt 0) {
Write-Host "Skipped removal of $notRemoved stale assignments for following resources:" -ForegroundColor Yellow
foreach($id in $notRemovedIds) {
Write-Host "- $id" -ForegroundColor Yellow
}
}
Some of my readers may wonder why I filter my resource graph query on the tenant id at all. This is important in scenarios where you might have access to different tenants after you authorized your shell (see Azure Lighthouse).
A further improvement could be to utilize resource graph and iterate over all resources too because they too could have explicit role assignments (seeing this you now should see why this might be a bad idea actually!). Also removing and re-adding the locks might ne an option.