added documetation

This commit is contained in:
Jurjen Ladenius
2025-11-03 08:12:01 +01:00
parent 8840b0e300
commit a226ca97ac
37 changed files with 8315 additions and 1481 deletions

View File

@@ -1,67 +1,225 @@
<#
.SYNOPSIS
Comprehensive Azure RBAC assignment analysis across entire Azure tenant.
.DESCRIPTION
This script analyzes RBAC assignments across all Azure resources in an Azure tenant,
providing a complete inventory of role assignments at every level of the Azure
resource hierarchy. The script generates a detailed CSV report with comprehensive
metadata including resource information, role assignments, and organizational tags.
Features:
• Recursive analysis of Management Groups, Subscriptions, Resource Groups, and Resources
• Complete RBAC assignment enumeration with role and principal details
• Organizational metadata collection (tags, locations, resource types)
• Hierarchical resource context (management group → subscription → resource group → resource)
• Timestamped CSV output for historical tracking and compliance reporting
• Comprehensive error handling to continue processing despite individual failures
.PARAMETER SubscriptionIds
Optional array of specific subscription IDs to process. If not provided, all active
subscriptions across all management groups will be processed. When specified, only
the listed subscriptions will be analyzed for RBAC assignments.
.PARAMETER OutputPath
Optional custom path for the output CSV file. If not provided, the file will be
created in the current directory with a timestamped filename.
.EXAMPLE
.\AzureRBAC.ps1
Process all management groups and subscriptions to generate complete RBAC inventory.
.EXAMPLE
.\AzureRBAC.ps1 -SubscriptionIds @("a134faf1-7a89-4f2c-8389-06d00bd5e2a7", "30ce4e64-4299-4b93-91b8-4c953f63678e")
Process only the specified subscriptions for RBAC analysis.
.EXAMPLE
.\AzureRBAC.ps1 -SubscriptionIds @("12345678-1234-1234-1234-123456789012") -OutputPath "C:\Reports\rbac-analysis.csv"
Process a specific subscription and save results to a custom location.
.OUTPUTS
CSV file: [YYYY-MM-DD HHMM] azure_rbac_assignments.csv
The output file contains the following columns:
- ResourceId: Azure resource identifier
- Id: Resource ID (for individual resources)
- Kind: Resource type (ManagementGroup, Subscription, ResourceGroup, Resource)
- Location: Azure region/location
- ResourceName: Name of the resource
- ResourceGroupName: Resource group containing the resource
- ResourceType: Azure resource type (e.g., Microsoft.Storage/storageAccounts)
- ManagementGroupId: Parent management group identifier
- ManagementGroupName: Parent management group display name
- SubscriptionId: Subscription identifier
- SubscriptionName: Subscription display name
- Tag_Team: Team tag value for organizational tracking
- Tag_Product: Product tag value for product alignment
- Tag_Environment: Environment tag value (Dev, Test, Prod, etc.)
- Tag_Data: Data classification tag value
- Tag_Delete: Deletion schedule tag value
- Tag_Split: Cost allocation split tag value
- RBAC_RoleAssignmentId: Unique role assignment identifier
- RBAC_Scope: Scope where the role assignment is effective
- RBAC_DisplayName: Display name of the assigned principal
- RBAC_SignInName: Sign-in name/UPN of the assigned principal
- RBAC_RoleDefinitionName: Name of the assigned Azure role
.NOTES
Requires PowerShell modules: Az.Accounts, Az.Resources
Requires appropriate Azure RBAC permissions:
• Reader access or higher on Management Groups
• Reader access or higher on Subscriptions
• Reader access or higher on Resource Groups and Resources
• Microsoft.Authorization/roleAssignments/read permission at appropriate scopes
Authentication:
• Must be authenticated to Azure (Connect-AzAccount) before running
• Service Principal or Managed Identity authentication supported
• Requires appropriate tenant-level permissions for comprehensive analysis
Performance Considerations:
• Processing time scales with number of resources and role assignments
• Large tenants may require several minutes to hours for complete analysis
• Network connectivity and API throttling may affect processing speed
• Memory usage scales with number of resources processed
Compatibility:
• PowerShell 5.1 and PowerShell 7.x
• Azure PowerShell module version 8.0 or later
• Windows, macOS, and Linux support
Output Management:
• CSV files are appended to support incremental data collection
• Timestamped filenames prevent overwrites
• Large result sets may generate substantial file sizes
• Consider data retention and storage management policies
.LINK
https://docs.microsoft.com/en-us/azure/role-based-access-control/
https://docs.microsoft.com/en-us/powershell/azure/
AUTHOR: Cloud Engineering Team
CREATED: Azure governance and compliance toolkit
VERSION: 2.0 - Simplified RBAC analysis without PIM detection
UPDATED: Focused on core RBAC assignment enumeration and organizational metadata
#>
# Script parameters
param(
[Parameter(Mandatory = $false, HelpMessage = "Array of subscription IDs to process. If not specified, all subscriptions will be processed.")]
[string[]]$SubscriptionIds = @(),
[Parameter(Mandatory = $false, HelpMessage = "Custom output path for the CSV file. If not specified, uses timestamped filename in current directory.")]
[string]$OutputPath = ""
)
#Connect-AzAccount
Import-Module Az.Accounts
Import-Module Az.Resources
# PowerShell class to represent Azure resource and RBAC assignment data
class ResourceCheck {
[string] $ResourceId = ""
[string] $Id = ""
[string] $Kind = ""
[string] $Location = ""
[string] $ResourceName = ""
[string] $ResourceGroupName = ""
[string] $ResourceType = ""
[string] $ManagementGroupId = ""
[string] $ManagementGroupName = ""
[string] $SubscriptionId = ""
[string] $SubscriptionName = ""
[string] $Tag_Team = ""
[string] $Tag_Product = ""
[string] $Tag_Environment = ""
[string] $Tag_Data = ""
[string] $Tag_Delete = ""
[string] $Tag_Split = ""
[string] $RBAC_RoleAssignmentId = ""
[string] $RBAC_Scope = ""
[string] $RBAC_DisplayName = ""
[string] $RBAC_SignInName = ""
[string] $RBAC_RoleDefinitionName = ""
# Resource identification and metadata
[string] $ResourceId = "" # Azure resource identifier (full ARM path)
[string] $Id = "" # Resource ID (used for individual resources)
[string] $Kind = "" # Resource level (ManagementGroup, Subscription, ResourceGroup, Resource)
[string] $Location = "" # Azure region/location where resource is deployed
[string] $ResourceName = "" # Name of the resource
[string] $ResourceGroupName = "" # Resource group containing the resource
[string] $ResourceType = "" # Azure resource type (e.g., Microsoft.Storage/storageAccounts)
# Organizational hierarchy context
[string] $ManagementGroupId = "" # Parent management group identifier
[string] $ManagementGroupName = "" # Parent management group display name
[string] $SubscriptionId = "" # Subscription identifier
[string] $SubscriptionName = "" # Subscription display name
# Organizational metadata tags (customize based on organizational tagging strategy)
[string] $Tag_Team = "" # Team responsible for the resource
[string] $Tag_Product = "" # Product/application alignment
[string] $Tag_Environment = "" # Environment classification (Dev, Test, Prod, etc.)
[string] $Tag_Data = "" # Data classification level
[string] $Tag_Delete = "" # Scheduled deletion information
[string] $Tag_Split = "" # Cost allocation split information
# RBAC assignment details
[string] $RBAC_RoleAssignmentId = "" # Unique identifier for the role assignment
[string] $RBAC_Scope = "" # Scope where the role assignment is effective
[string] $RBAC_DisplayName = "" # Display name of the assigned principal
[string] $RBAC_SignInName = "" # Sign-in name/UPN of the assigned principal
[string] $RBAC_RoleDefinitionName = "" # Name of the assigned Azure role
}
# Display script execution banner
Write-Host "========================================================================================================================================================================"
Write-Host "Creating resource RBAC assignment overview."
Write-Host "========================================================================================================================================================================"
Write-Host "Azure RBAC Assignment Analysis"
Write-Host "========================================================================================================================================================================"
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date azure_rbac_assignments.csv"
# Display processing scope information
if ($SubscriptionIds.Count -gt 0) {
Write-Host "SCOPE: Processing specific subscriptions only"
Write-Host "Subscription IDs to process:"
foreach ($subId in $SubscriptionIds) {
Write-Host " - $subId"
}
} else {
Write-Host "SCOPE: Processing ALL active subscriptions across all management groups"
}
Write-Host ""
# Generate filename for output CSV (use custom path or default timestamped filename)
if ($OutputPath -ne "") {
$fileName = $OutputPath
Write-Host "Using custom output file: $fileName"
} else {
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date azure_rbac_assignments.csv"
Write-Host "Using default timestamped filename: $fileName"
}
# Discover all management groups in the tenant
# This provides the top-level organizational structure for Azure resources
$managementGroups = Get-AzManagementGroup
# Process each management group in the tenant
foreach ($managementGroup in $managementGroups)
{
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
Write-Host "Management group [$($managementGroup.Name)]"
# Initialize collection for management group level role assignments
[ResourceCheck[]]$Result = @()
try {
# Get role assignments directly assigned to this management group (not inherited)
$roleAssignments = Get-AzRoleAssignment -Scope $managementGroup.Id | Where-Object Scope -eq $managementGroup.Id
# Process each role assignment at the management group level
foreach($roleAssignment in $roleAssignments) {
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.ResourceId = ""
$resourceCheck.Kind = "ManagementGroup"
$resourceCheck.Location = ""
$resourceCheck.ResourceGroupName = ""
# Set management group context (no individual resource context at this level)
$resourceCheck.ResourceId = "" # No specific resource for MG assignments
$resourceCheck.Kind = "ManagementGroup" # Indicates this is a management group level assignment
$resourceCheck.Location = "" # Management groups don't have locations
$resourceCheck.ResourceGroupName = "" # No resource group context
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = ""
$resourceCheck.SubscriptionId = "" # No subscription context at MG level
$resourceCheck.SubscriptionName = ""
# Management groups don't have tags in the same way resources do
$resourceCheck.Tag_Team = ""
$resourceCheck.Tag_Product = ""
$resourceCheck.Tag_Environment = ""
$resourceCheck.Tag_Data = ""
$resourceCheck.Tag_Delete = ""
$resourceCheck.Tag_Split = ""
# Populate RBAC assignment details
$resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId
$resourceCheck.RBAC_Scope = $roleAssignment.Scope
$resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName
@@ -75,39 +233,68 @@ foreach ($managementGroup in $managementGroups)
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
$subscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name | Where-Object State -eq "Active"
# Get all active subscriptions under this management group
$allSubscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name | Where-Object State -eq "Active"
# Filter subscriptions if specific subscription IDs were provided
if ($SubscriptionIds.Count -gt 0) {
$subscriptions = $allSubscriptions | Where-Object {
$subscriptionId = $_.Id.Split('/')[-1] # Extract subscription ID from resource path
$subscriptionId -in $SubscriptionIds
}
if ($subscriptions.Count -eq 0) {
Write-Host "No matching subscriptions found in management group '$($managementGroup.DisplayName)' for the specified subscription IDs."
continue
}
Write-Host "Processing $($subscriptions.Count) filtered subscription(s) in management group '$($managementGroup.DisplayName)'"
} else {
$subscriptions = $allSubscriptions
Write-Host "Processing all $($subscriptions.Count) active subscription(s) in management group '$($managementGroup.DisplayName)'"
}
# Process each subscription under the current management group
foreach ($subscription in $subscriptions)
{
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
# Extract subscription ID from the full resource path
$scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length)
$subscriptionId = $scope.Replace("/subscriptions/", "")
Write-Host "Subscription [$($subscription.DisplayName) - $subscriptionId]"
# Set Azure PowerShell context to the current subscription
Set-AzContext -SubscriptionId $subscriptionId | Out-Null
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
# Initialize result array for this subscription's role assignments
[ResourceCheck[]]$Result = @()
# Get role assignments directly assigned to this subscription scope
try {
$roleAssignments = Get-AzRoleAssignment -Scope $scope | Where-Object Scope -eq $scope
# Process each subscription-level role assignment
foreach($roleAssignment in $roleAssignments) {
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.ResourceId = ""
$resourceCheck.Kind = "Subscription"
$resourceCheck.Location = ""
$resourceCheck.ResourceGroupName = ""
# Set subscription context (no individual resource context at this level)
$resourceCheck.ResourceId = "" # No specific resource for subscription assignments
$resourceCheck.Kind = "Subscription" # Indicates this is a subscription level assignment
$resourceCheck.Location = "" # Subscriptions don't have specific locations
$resourceCheck.ResourceGroupName = "" # No resource group context at subscription level
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscription.Id
$resourceCheck.SubscriptionName = $subscription.DisplayName
# Extract subscription-level tags if available
$resourceCheck.Tag_Team = $subscription.Tags.team
$resourceCheck.Tag_Product = $subscription.Tags.product
$resourceCheck.Tag_Environment = $subscription.Tags.environment
$resourceCheck.Tag_Data = $subscription.Tags.data
$resourceCheck.Tag_Delete = $subscription.Tags.delete
$resourceCheck.Tag_Split = $subscription.Tags.split
# Populate RBAC assignment details
$resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId
$resourceCheck.RBAC_Scope = $roleAssignment.Scope
$resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName
@@ -117,34 +304,46 @@ foreach ($managementGroup in $managementGroups)
$Result += $resourceCheck
}
} catch {
# Silently handle any errors during subscription-level role assignment processing
}
# Export subscription-level role assignments to CSV
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
# Get all resource groups within the current subscription
$resourceGroups = Get-AzResourceGroup
# Process each resource group for RBAC assignments
foreach ($resourceGroup in $resourceGroups) {
# Initialize result array for this resource group's role assignments
[ResourceCheck[]]$Result = @()
# Get role assignments at resource group level and below
try {
$roleAssignments = Get-AzRoleAssignment -Scope $resourceGroup.ResourceId | Where-Object Scope -Like "$($resourceGroup.ResourceId)*"
# Process each resource group-level role assignment
foreach($roleAssignment in $roleAssignments) {
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
# Set resource group context
$resourceCheck.ResourceId = $resourceGroup.ResourceId
$resourceCheck.Kind = "ResourceGroup"
$resourceCheck.Kind = "ResourceGroup" # Indicates this is a resource group level assignment
$resourceCheck.Location = $resourceGroup.Location
$resourceCheck.ResourceGroupName = $resourceGroup.ResourceGroupName
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscription.Id
$resourceCheck.SubscriptionName = $subscription.DisplayName
# Extract resource group-level tags if available
$resourceCheck.Tag_Team = $resourceGroup.Tags.team
$resourceCheck.Tag_Product = $resourceGroup.Tags.product
$resourceCheck.Tag_Environment = $resourceGroup.Tags.environment
$resourceCheck.Tag_Data = $resourceGroup.Tags.data
$resourceCheck.Tag_Delete = $resourceGroup.Tags.delete
$resourceCheck.Tag_Split = $resourceGroup.Tags.split
# Populate RBAC assignment details
$resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId
$resourceCheck.RBAC_Scope = $roleAssignment.Scope
$resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName
@@ -154,38 +353,51 @@ foreach ($managementGroup in $managementGroups)
$Result += $resourceCheck
}
} catch {
# Silently handle any errors during resource group-level role assignment processing
}
# Export resource group-level role assignments to CSV
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
}
# Get all individual resources within the current subscription
$allResources = Get-AzResource
# Process each individual resource for RBAC assignments
foreach ($resource in $allResources) {
# Initialize result array for this resource's role assignments
[ResourceCheck[]]$Result = @()
# Get role assignments directly assigned to this specific resource
try {
$roleAssignments = Get-AzRoleAssignment -Scope $resource.ResourceId | Where-Object Scope -eq $resource.ResourceId
# Process each resource-level role assignment
foreach($roleAssignment in $roleAssignments) {
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
# Set individual resource context with full metadata
$resourceCheck.ResourceId = $resource.ResourceId
$resourceCheck.Id = $resource.Id
$resourceCheck.Kind = "Resource"
$resourceCheck.Id = $resource.Id # Additional resource identifier
$resourceCheck.Kind = "Resource" # Indicates this is an individual resource assignment
$resourceCheck.Location = $resource.Location
$resourceCheck.ResourceName = $resource.ResourceName
$resourceCheck.ResourceGroupName = $resource.ResourceGroupName
$resourceCheck.ResourceType = $resource.ResourceType
$resourceCheck.ResourceType = $resource.ResourceType # e.g., Microsoft.Storage/storageAccounts
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscription.Id
$resourceCheck.SubscriptionName = $subscription.DisplayName
# Extract resource-level tags if available
$resourceCheck.Tag_Team = $resource.Tags.team
$resourceCheck.Tag_Product = $resource.Tags.product
$resourceCheck.Tag_Environment = $resource.Tags.environment
$resourceCheck.Tag_Data = $resource.Tags.data
$resourceCheck.Tag_Delete = $resource.Tags.delete
$resourceCheck.Tag_Split = $resource.Tags.split
# Populate RBAC assignment details
$resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId
$resourceCheck.RBAC_Scope = $roleAssignment.Scope
$resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName
@@ -195,11 +407,20 @@ foreach ($managementGroup in $managementGroups)
$Result += $resourceCheck
}
} catch {
# Silently handle any errors during individual resource-level role assignment processing
}
# Export individual resource-level role assignments to CSV
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
}
}
}
# Final completion message
Write-Host "========================================================================================================================================================================"
if ($SubscriptionIds.Count -gt 0) {
Write-Host "RBAC analysis complete for $($SubscriptionIds.Count) specified subscription(s)."
} else {
Write-Host "RBAC analysis complete for all active subscriptions across all management groups."
}
Write-Host "Results exported to: $fileName"
Write-Host "Done."