Azure Role Assignment Hygiene

Introduction

Hello, Azure enthusiasts! I'm back again with another insightful blog post. My name is Felipe Binotto, Cloud Solution Architect, based in Australia.

Today, we're going to delve into a topic that is often overlooked but is critical to the smooth operation of your Azure environment – Azure Role Assignment Hygiene.

I will not cover guest user accounts as my colleague @Bruno Gabrielli  has already covered it in detail in this great post.

What is Role Assignment Hygiene

Azure Role Assignment Hygiene refers to the practice of regularly reviewing and cleaning up Azure role assignments. This includes removing orphaned permissions, i.e., permissions that are no longer in use or are associated with non-existent users or groups. We are also going one step further and remove permissions for disabled users, including disabled users which are part of an group or even a nested group!

Maintaining good Azure Role Assignment Hygiene is crucial for several reasons:

  • Security: Orphaned permissions can pose a security risk if they are exploited by malicious actors.
  • Compliance: Regular cleanup of role assignments can help meet compliance requirements.
  • Simplicity: A clean and well-managed role assignment structure is easier to understand and manage.

To achieve our goals, I will present you with two separate . I recommend you use those as Azure runbooks and run them on a schedule to ensure you maintain a high level of role assignment hygiene.

The will retrieve role assignments for every single Azure object, including Management Groups, Subscriptions, Resource Groups and Resources. Because I'm using an Azure Account to execute those scripts, I added the identity of the Account to the User Access Administrator role at the top Management Group scope. This role is required for both scripts. The second script also requires either the Groups Administrator or User Administrator role.

The scripts were tested on PowerShell 5.1.

Let's look at the scripts and understand how they work.

Remove Orphaned Role Assignments script

Pre-Requisites:

  • Automation Account identity requires User Access Administrator role at the top Management Group scope.
  • Az.Accounts and Az.Resources PowerShell modules
$null = Connect-AzAccount -Identity
$subscriptions = Get-AzSubscription | Where-Object State -eq “Enabled”
foreach($subscription in $subscriptions) {
Select-AzSubscription $subscription | Out-Null
Write-Output $subscription.Name
Get-AzRoleAssignment | Where-Object ObjectType -eq “Unknown” | Remove-AzRoleAssignment
}
$managementGroups = Get-AzManagementGroup
foreach($managementGroup in $managementGroups) {
Write-Output $managementGroup.Name
Get-AzRoleAssignment -Scope $managementGroup.id | Where-Object ObjectType -eq “Unknown” | Remove-AzRoleAssignment
}

The script does the following:

  1. Connects to Azure using a managed identity.
  2. Gets all subscriptions which are active.
  3. Then for each subscription, it gets role assignments (which include Resource Groups and Resources) which have the ObjectType property set as Unknown and removes them.
  4. Gets all management groups.
  5. Then for each management group, it gets role assignments which have the ObjectType property set as Unknown and removes them.

Remove Role Assignments for Disabled Users script

Pre-Requisites:

  • Automation Account identity requires User Access Administrator role at the top Management Group scope.
  • Automation Account identity requires User Administrator or Group Administrator role. You must add it to an Azure AD group to be able to assign it to the role. Alternatively, you can use a Service Principal instead of the Managed Identity.
  • Az.Accounts and Az.Resources PowerShell modules
# Connect to Azure with user-assigned managed identity
$null = Connect-AzAccount -Identity
$subscriptions = Get-AzSubscription | Where-Object State -eq “Enabled”
$disabledUsers = Get-AzADUser -Filter “AccountEnabled eq false”
$permissions = @()
foreach($subscription in $subscriptions) {
Select-AzSubscription $subscription | Out-Null
Write-Output “Retrieving subscription, resource group and resource level permissions for subscription: $($subscription.Name)”
$permissions += Get-AzRoleAssignment
}
$managementGroups = Get-AzManagementGroup
foreach($managementGroup in $managementGroups) {
Write-Output “Retrieving management group level permissions for management group: $($managementGroup.Name)”
$permissions += Get-AzRoleAssignment -Scope $managementGroup.id
}
$userPermissions = $permissions | Where-Object ObjectType -eq “User” | Sort-Object roleAssignmentId -unique
$groupPermissions = $permissions | Where-Object ObjectType -eq “Group” | Sort-Object roleAssignmentId -unique
$allGroupsPermissions = $groupPermissions | Sort-Object ObjectId -unique
$countOfGroups = $allGroupsPermissions.count
$allUsersPermissions = $userPermissions | Sort-Object ObjectId -unique
foreach($permission in $allUsersPermissions){
if($permission.objectid -in $disabledUsers.id){
Remove-AzRoleAssignment -ObjectId $permission.ObjectId -Scope $permission.Scope -RoleDefinitionName $permission.RoleDefinitionName
Write-Output “Removing role assignment for user: $($permission.DisplayName) on scope: $($permission.Scope)”
}
}
function Get-AzureADGroupMemberNested($ObjectId) {
#Get the members of this group
$users = @()
$members = Get-AzADGroupMember -GroupObjectId $ObjectId -WarningAction Ignore
foreach ($member in $members) {
if ($member.ODataType -eq “#microsoft.graph.group” -and $member.id -ne $ObjectID) {
$skip = Get-AzADGroup -ObjectId $member.id
if($skip.AdditionalProperties.onPremisesLastSyncDateTime -or $skip.MembershipRule -or !$skip.SecurityEnabled){
Write-Output “Skipping group: $($skip.DisplayName) which is dynamic or synchronized or not security enabled.”
}
else{
#If member is a group then recursively look at group membership
Get-AzureADGroupMemberNested -ObjectId $member.ID
}
}
elseif ($member.ODataType -eq “#microsoft.graph.user”) {
$groupToQuery = @{}
$groupToQuery = $groupToQuery | Select-Object displayname, id, groupid
$groupToQuery.displayname = $member.DisplayName
$groupToQuery.id = $member.id
$groupToQuery.groupid = $ObjectID
$users += $groupToQuery
}
}
return $users
}
$count = 1
foreach($group in $allGroupsPermissions){
$skip = Get-AzADGroup -ObjectId $group.ObjectId
if($skip.AdditionalProperties.onPremisesLastSyncDateTime -or $skip.MembershipRule -or !$skip.SecurityEnabled){
Write-Output “Skipping group: $($skip.DisplayName) which is dynamic or synchronized or not security enabled.”
}
else {
Write-Output “Querying AAD group: $($group.DisplayName) which is group $count out of $countOfGroups groups.”
$groupToQuery = Get-AzureADGroupMemberNested -ObjectId $group.ObjectId
foreach($principalId in $groupToQuery){
if($principalId.id -in $disabledUsers.Id){
Remove-AzADGroupMember -MemberObjectId $principalId.id -GroupObjectId $principalId.groupid -WarningAction Ignore -WhatIf
Write-Output “Removing user: $($principalId.DisplayName) from AAD group: $($principalId.groupid)”
}
}
}
$count++
}

The script does the following:

  1. Connects to Azure using a managed identity.
  2. Gets all subscriptions which are active.
  3. Gets all users which are disabled in Azure AD.
  4. Gets all role assignments for subscriptions (which include Resource Groups and Resources) and assign them to an array.
  5. Gets all role assignments for management groups and assign them to the same array.
  6. Split the permissions between User and Group permissions and only select unique Role Assignments and unique ObjectIDs.
  7. Then for each of the user permissions, if the user (which has a role assignment) is part of the $disabledUsers, we remove the role assignment.
  8. We define a little function to deal with nested groups.
  9. For each group permission, we first check if it is a group we want/can make changes to.
  10. Then we retrieve all users from the group (including from nested groups) and if the user (which has a role assignment) is part of the $disabledUsers, we remove the user from the group (which effectively removes the permission for that specific user).

Conclusion

Maintaining Azure Role Assignment Hygiene is a crucial aspect of managing your Azure environment. It helps to ensure security and compliance while simplifying the management process.

The scripts I've shared today are a powerful tool to automate the cleanup process. They remove orphaned permissions and disabled users, thereby reducing potential security risks. It's important to note that these scripts should be used as part of a broader strategy for Azure Role Assignment Hygiene, which includes regular reviews and audits of role assignments.

Remember, a clean Azure environment is a happy Azure environment!

Stay tuned for more posts on optimize your Azure experience. As always, feel free to leave any questions or comments below. Happy cloud computing!

I hope this was informative to you and thanks for reading!

Disclaimer

The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.

 

This article was originally published by Microsoft's Windows Security Blog. You can find the original article here.