Azure Role Assignment Hygiene
Published Jul 16 2023 07:12 PM 5,465 Views
Microsoft

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 Azure AD 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 scripts. I recommend you use those scripts as Azure Automation runbooks and run them on a schedule to ensure you maintain a high level of role assignment hygiene.

 

The scripts will retrieve role assignments for every single Azure object, including Management Groups, Subscriptions, Resource Groups and Resources. Because I’m using an Azure Automation Account to execute those scripts, I added the identity of the Automation 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 Azure AD 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 Azure AD 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 how to 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.

2 Comments
Co-Authors
Version history
Last update:
‎Jul 16 2023 07:12 PM
Updated by: