tests/Test-Assessment.21813.ps1
|
<# .SYNOPSIS Checks the ratio of Global Administrator assignments to total privileged role assignments in the tenant. #> function Test-Assessment-21813{ [ZtTest( Category = 'Privileged access', ImplementationCost = 'Medium', Pillar = 'Identity', RiskLevel = 'High', SfiPillar = 'Protect identities and secrets', TenantType = ('Workforce'), TestId = 21813, Title = 'High Global Administrator to privileged user ratio', UserImpact = 'Low' )] [CmdletBinding()] param( $Database ) Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Checking Global Administrator to privileged user ratio' Write-ZtProgress -Activity $activity -Status 'Getting privileged role assignments' $globalAdminRoleId = '62e90394-69f5-4237-9190-012177145e10' # Query all privileged role assignments from the database $sql = @" SELECT principalId, principalDisplayName, userPrincipalName, roleDisplayName, roleDefinitionId, privilegeType, isPrivileged, "@odata.type" FROM vwRole WHERE isPrivileged = 1 AND "@odata.type" = '#microsoft.graph.user' "@ $roleAssignments = Invoke-DatabaseQuery -Database $Database -Sql $sql Write-PSFMessage "Found $($roleAssignments.Count) privileged role assignment records for users" -Level Verbose # Build user-role map (deduplicating users across active/eligible assignments) # Per requirements: Separate de-duplication for GA vs non-GA privileged roles # A user can be counted in BOTH buckets if they have GA + other privileged roles $allGAUsers = @{} # Users with GA role (deduplicated by principalId) $allPrivilegedUsers = @{} # Users with non-GA privileged roles (deduplicated by principalId) $userRoleMap = @{} # Key: userId, Value: @{ user = userObject, roles = @(roleNames), isGA = $bool } foreach ($assignment in $roleAssignments) { $userId = $assignment.principalId $isGARole = $assignment.roleDefinitionId -eq $globalAdminRoleId # IMPORTANT: A user can be in BOTH buckets # Add to GA bucket if this is a GA role if ($isGARole) { $allGAUsers[$userId] = $assignment } # Add to non-GA privileged bucket if this is NOT a GA role if (-not $isGARole) { $allPrivilegedUsers[$userId] = $assignment } # Also maintain userRoleMap for display purposes if (-not $userRoleMap.ContainsKey($userId)) { $userRoleMap[$userId] = @{ user = $assignment roles = [System.Collections.ArrayList]@() isGA = $false } } # Add role to user's role list (avoid duplicates) if ($assignment.roleDisplayName -notin $userRoleMap[$userId].roles) { [void]$userRoleMap[$userId].roles.Add($assignment.roleDisplayName) } # Mark if user is a Global Administrator if ($isGARole) { $userRoleMap[$userId].isGA = $true Write-PSFMessage "Marked user $($assignment.principalDisplayName) as Global Administrator" -Level Verbose } } # Separate counts for GA and non-GA privileged roles $gaRoleAssignmentCount = $allGAUsers.Count $privilegedRoleAssignmentCount = $allPrivilegedUsers.Count $totalPrivilegedRoleAssignmentCount = $gaRoleAssignmentCount + $privilegedRoleAssignmentCount # Calculate percentages if ($totalPrivilegedRoleAssignmentCount -gt 0) { $gaPercentage = [math]::Round(($gaRoleAssignmentCount / $totalPrivilegedRoleAssignmentCount) * 100, 2) $otherPercentage = [math]::Round(($privilegedRoleAssignmentCount / $totalPrivilegedRoleAssignmentCount) * 100, 2) } else { $gaPercentage = 0 $otherPercentage = 0 } # Determine status indicator if ($gaPercentage -lt 30) { $statusIndicator = '✅ Passed' $hasHealthyRatio = $true } elseif ($gaPercentage -ge 30 -and $gaPercentage -lt 50) { $statusIndicator = '⚠️ Investigate' $hasModerateRatio = $true } else { $statusIndicator = '❌ Failed' $hasHighRatio = $true } # Build simplified markdown output $mdInfo = "`n## Privileged role assignment summary`n`n" $mdInfo += "**Global administrator role count:** $gaRoleAssignmentCount ($gaPercentage%) - $statusIndicator`n`n" $mdInfo += "**Other privileged role count:** $privilegedRoleAssignmentCount ($otherPercentage%)`n`n" # Build user table - sorted with GAs first, then by display name within each group $mdInfo += "## User privileged role assignments`n`n" $mdInfo += "| User | Global administrator | Other Privileged Role(s) |`n" $mdInfo += "| :--- | :------------------- | :------ |`n" # Sort users: GAs first (by name), then non-GAs (by name) $sortedUsers = $userRoleMap.Values | Sort-Object @{Expression = {-not $_.isGA}}, @{Expression = {$_.user.principalDisplayName}} foreach ($userEntry in $sortedUsers) { $user = $userEntry.user $isGA = if ($userEntry.isGA) { 'Yes' } else { 'No' } # Get non-GA roles (exclude Global Administrator from the list) $otherRoles = $userEntry.roles | Where-Object { $_ -ne 'Global Administrator' } | Sort-Object $rolesList = if ($otherRoles.Count -gt 0) { ($otherRoles -join ', ') } else { '-' } # Create clickable user link $userLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/AdministrativeRole/userId/$($user.principalId)/hidePreviewBanner~/true" $safeDisplayName = Get-SafeMarkdown -Text $user.principalDisplayName $displayNameLink = "[$safeDisplayName]($userLink)" $mdInfo += "| $displayNameLink | $isGA | $rolesList |`n" } if ($userRoleMap.Count -eq 0) { $mdInfo += "| No privileged users found | - | - |`n" } #region Assessment Logic Write-PSFMessage "Assessment data: GACount=$gaRoleAssignmentCount, OtherPrivilegedCount=$privilegedRoleAssignmentCount, TotalCount=$totalPrivilegedRoleAssignmentCount, GARatio=$gaPercentage%" -Level Verbose if ($totalPrivilegedRoleAssignmentCount -eq 0) { $passed = $true $testResultMarkdown = "No privileged role assignments found in the tenant.$mdInfo" } elseif ($hasHealthyRatio) { $passed = $true $testResultMarkdown = "Less than 30% of privileged role assignments in the tenant are Global Administrator.$mdInfo" } elseif ($hasModerateRatio) { $passed = $false $customStatus = 'Investigate' $testResultMarkdown = "Between 30-50% of privileged role assignments in the tenant are Global Administrator.$mdInfo" } else { $passed = $false $testResultMarkdown = "More than 50% of privileged role assignments in the tenant are Global Administrator.$mdInfo" } #endregion Assessment Logic $params = @{ TestId = '21813' Status = $passed Result = $testResultMarkdown } # Only add CustomStatus when it's "Investigate" (30-50% GA ratio) if ($hasModerateRatio) { $params.CustomStatus = $customStatus } Add-ZtTestResultDetail @params } |