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
Kass Eisenmenger
comments powered by Disqus