Recursive Azure Roles Report Powershell
Introduction
A few days back I was tasked at work with automating some audit information for our new azure environments and in scripting this out I ran into some issues that slowed me down for a few days. Here is how I ended up solving the issue.
** Note: This script is pretty rough but it works great for what I needed.
The Problem
The task was to export a list of all of the IAM roles that users and groups were assigned to in Azure for our regular reviews. Sounds Pretty simple at first however Azure Powershell lacks a way of getting members of groups recursively and the output of the get roles command is a bit rough for a report and needed a bit of work.
The solution
Section 1: Classes
The first section of this is to setup some classes to make the data look nicer on export
# This class defines the data in each row of our export
class RoleAssignment {
[string]$Scope # The scope of the Role Assignment
[string]$Name # The name of the object assigned to the role
[string]$RoleName # The name of the roll
[string]$ObjectType # The type of object IE User,Group,Service Principle
}
# This class defines the list of subscriptions to query roles for
class SubscriptionListItem {
[string]$Name # The name of the subscription. This will define the name of the tab in the excel export
[string]$SubscriptionId # The ID for the subscription
}
Section 2: Variables
The next section sets some variables that will control the subscriptions we pull the information we need from. In my case we have 6 different subscriptions to pull roles for.
$subscription = @(
[SubscriptionListItem]@{Name="a"; SubscriptionId="037ad4af-40d3-43d0-8d4c-e5cd792b4971" },
[SubscriptionListItem]@{Name="b"; SubscriptionId="bf61fe48-52c0-4f8e-9c43-66941403e03a"},
[SubscriptionListItem]@{Name="c"; SubscriptionId="718c9d40-312a-4289-941a-5ce29ac6cb8a"},
[SubscriptionListItem]@{Name="d"; SubscriptionId="244c15e9-be4e-4d13-b1f8-3a8756d085c7"},
[SubscriptionListItem]@{Name="e"; SubscriptionId="7ce05ff8-b14a-4186-abb5-822c064be037"},
[SubscriptionListItem]@{Name="f"; SubscriptionId="4c91c567-4224-46fc-a089-84b54822aff9"}
);
Section 3: Functions
Here we define a function to get the users from a group recursively and return all the users in a list of RoleListItem. This will append the name of the previous group to the name column to see groups recursively in the output
function Get-RecursiveAzureAdGroupMembers {
param (
[parameter(Mandatory=$true,ValueFromPipeline=$true)]$Group,
[parameter(Mandatory=$true,ValueFromPipeline=$false)]$role,
[parameter(Mandatory=$false,ValueFromPipeline=$false)]$ParentGroupName
)
Process{
$output = New-Object System.Collections.Generic.List[RoleAssignment]; # Create a new output list
$members = Get-AzureRmADGroupMember -GroupObjectId $Group.Id; # Get all the members of the group
$scopeSplit = $role.Scope.Split("/"); # Split the scope
$scope = $scopeSplit[$scopeSplit.Count -1]; # Grab the last item in the split for a clean scope item
$users = $members | Where-Object{$_.Type -eq "User"}; # Grab all the users from the member list
# Add only the users to the output
foreach ($user in $users) {
$name = $ParentGroupName + ":" + $user.DisplayName;
$output.Add([RoleAssignment]@{Scope = $scope; Name = $name; RoleName = $role.RoleDefinitionName; ObjectType = $role.ObjectType });
}
# If there are any groups in the list
if ($members | Where-Object {$_.Type -eq "Group"}) {
# Return the members of the group
$output += ($members | Where-Object {$_.Type -eq "Group"} | ForEach-Object {
$name = $ParentGroupName + ":" + $_.DisplayName; # Prepend the previous groups name if any
Get-RecursiveAzureAdGroupMembers -Group $_ -role $role -ParentGroupName $name; # Run this same function over and over for each group recursively
})
}
}
End {
Return $output
}
}
Section 4: The logic
Now for the fun part. Now I loop through the list of subscriptions and filter to the different resource groups and subscriptions that I want from my data
foreach ($subscription in $subscriptions){
$output = New-Object System.Collections.Generic.List[RoleAssignment]; # Create empty list of RoleAssignment
Set-AzureRmContext -Subscription $subscription.SubscriptionId; # Set the context of the script to the current subscription in the loop
$roleAssignments = Get-AzureRmRoleAssignment; # Get all of the role assignments
$filteredAssignments = $roleAssignments | Where-Object { # Filter the rows to the wanted subscriptions, management groups, and resource groups and remove service principals as we only wanted users and groups
$_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)" `
-or $_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)/resourcegroups/1" `
-or $_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)/resourcegroups/2" `
-or $_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)/resourcegroups/3" `
-or $_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)/resourcegroups/4" `
-or $_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)/resourcegroups/5" `
-or $_.Scope -eq "/subscriptions/$($subscription.SubscriptionId)/resourcegroups/6" `
-or $_.Scope -like "*mg-root"} | Where-Object {$_.ObjectType -ne "ServicePrincipal"};
$users = $filteredAssignments | Where-Object {$_.ObjectType -eq "User"}; # Grab all the users from the filtered data
$groups = $filteredAssignments | Where-Object {$_.ObjectType -eq "Group"}; # Grab all the Groups from the filtered data
foreach ($user in $users) {# Loop through the Users and add to the output
$scopeSplit = $user.Scope.Split("/");
$scope = $scopeSplit[$scopeSplit.Count -1];
$output.Add([RoleAssignment]@{Scope = $scope; Name = $user.DisplayName; RoleName = $user.RoleDefinitionName; ObjectType = $user.ObjectType; });
}
foreach ($group in $groups) {# Loop through the groups and add to the output
$output += (Get-AzureRmADGroup -ObjectId $group.ObjectId | Get-RecursiveAzureAdGroupMembers -role $group -ParentGroupName $group.DisplayName);
}
# Export the gathered data to a new worksheet
$output | Export-Excel -WorksheetName $subscription.Name -Path "$PSScriptRoot\AzureRoleAssignments.xlsx" -Append -AutoSize -FreezeTopRow;
}
Parting notes
This script requires the use of the Export-Excel module which requires a copy of excel be installed on whatever machine this runs from.
You can install this module like so
Install-Module ImportExcel