Checks/Azure/Test-IamRoleUserAccessAdminRestricted.ps1

function Test-IamRoleUserAccessAdminRestricted {
    <#
    .SYNOPSIS
        Tests if the User Access Administrator role is properly restricted.

    .DESCRIPTION
        Checks for active assignments of the highly privileged 'User Access Administrator'
        role in Azure subscriptions. Reports on each role assignment individually.

        The User Access Administrator role grants the ability to manage access to Azure
        resources, which can lead to privilege escalation if not properly restricted.

        Built-in User Access Administrator role ID: 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9

    .PARAMETER CheckMetadata
        Hashtable containing check metadata from AzureChecks.json including:
        - id: Check identifier
        - severity: Severity level

    .OUTPUTS
        [PSCustomObject[]] Array of finding objects, one per role assignment.

    .NOTES
        Data source: $script:IAMService[$subscriptionId].RoleAssignments and RoleDefinitions
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$CheckMetadata
    )

    $ErrorActionPreference = 'Stop'

    # User Access Administrator built-in role definition ID
    $userAccessAdminRoleId = '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'

    foreach ($subscriptionId in $script:IAMService.Keys) {
        $iamData = $script:IAMService[$subscriptionId]

        # Check if role assignments were loaded
        if (-not $iamData.RoleAssignments) {
            $findingParams = @{
                CheckMetadata  = $CheckMetadata
                Status         = 'SKIPPED'
                StatusExtended = "Unable to retrieve role assignments for subscription $subscriptionId"
                ResourceId     = "/subscriptions/$subscriptionId"
                ResourceName   = "Subscription $subscriptionId"
            }
            New-CIEMFinding @findingParams
            continue
        }

        $roleAssignments = $iamData.RoleAssignments
        $roleDefinitions = $iamData.RoleDefinitions

        # Build a lookup of role definition IDs to names
        $roleNameLookup = @{}
        if ($roleDefinitions) {
            foreach ($role in $roleDefinitions) {
                $roleId = if ($role.PSObject.Properties['name']) { $role.name } else { $null }
                $roleName = if ($role.properties.PSObject.Properties['roleName']) {
                    $role.properties.roleName
                }
                else {
                    $null
                }
                if ($roleId -and $roleName) {
                    $roleNameLookup[$roleId] = $roleName
                }
            }
        }

        # Report on each role assignment
        foreach ($assignment in $roleAssignments) {
            # Extract assignment details (strict mode safe)
            $assignmentName = if ($assignment.PSObject.Properties['name']) {
                $assignment.name
            }
            else {
                'Unknown'
            }

            $roleDefinitionId = if ($assignment.properties.PSObject.Properties['roleDefinitionId']) {
                $assignment.properties.roleDefinitionId
            }
            else {
                $null
            }

            $principalId = if ($assignment.properties.PSObject.Properties['principalId']) {
                $assignment.properties.principalId
            }
            else {
                'Unknown'
            }

            $principalType = if ($assignment.properties.PSObject.Properties['principalType']) {
                $assignment.properties.principalType
            }
            else {
                'Unknown'
            }

            # Extract the role GUID from the full resource ID
            $roleGuid = if ($roleDefinitionId -match '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
                $matches[0]
            }
            else {
                $null
            }

            # Look up the role name
            $roleName = if ($roleGuid -and $roleNameLookup.ContainsKey($roleGuid)) {
                $roleNameLookup[$roleGuid]
            }
            else {
                ''
            }

            # Check if this is the User Access Administrator role
            $isUserAccessAdmin = ($roleName -eq 'User Access Administrator') -or ($roleGuid -eq $userAccessAdminRoleId)

            if ($isUserAccessAdmin) {
                $findingParams = @{
                    CheckMetadata  = $CheckMetadata
                    Status         = 'FAIL'
                    StatusExtended = "Role assignment $assignmentName in subscription $subscriptionId grants User Access Administrator role to $principalType $principalId."
                    ResourceId     = $assignment.id
                    ResourceName   = $assignmentName
                }
                New-CIEMFinding @findingParams
            }
            else {
                $findingParams = @{
                    CheckMetadata  = $CheckMetadata
                    Status         = 'PASS'
                    StatusExtended = "Role assignment $assignmentName in subscription $subscriptionId does not grant User Access Administrator role."
                    ResourceId     = $assignment.id
                    ResourceName   = $assignmentName
                }
                New-CIEMFinding @findingParams
            }
        }
    }
}