Checks/Azure/Test-EntraUserWithVmAccessHasMfa.ps1
|
function Test-EntraUserWithVmAccessHasMfa { <# .SYNOPSIS Tests if users with VM access have MFA enabled. .DESCRIPTION This check verifies that users with role assignments that grant access to virtual machines have MFA registered. This includes roles such as: - Virtual Machine Administrator Login - Virtual Machine User Login - Virtual Machine Contributor - Owner (at VM or resource group scope) - Contributor (at VM or resource group scope) This check requires IAM service data to identify users with VM-related role assignments. .PARAMETER CheckMetadata Hashtable containing check metadata including id and severity. .EXAMPLE Test-EntraUserWithVmAccessHasMfa -CheckMetadata $metadata #> [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [hashtable]$CheckMetadata ) $ErrorActionPreference = 'Stop' # VM-related role definition IDs (matches Prowler) $vmRoleDefinitionIds = @( 'b24988ac-6180-42a0-ab88-20f7382dd24c' # Contributor '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' # Owner '9980e02c-c2be-4d73-94e8-173b1dc7cf3c' # Virtual Machine Contributor '1c0163c0-47e6-4577-8991-ea5c82e286e4' # Virtual Machine Administrator Login 'fb879df8-f326-4884-b1cf-06f3ad86be52' # Virtual Machine User Login '602da2ba-a5c2-41da-b01d-5360126ab525' # Virtual Machine Local User Login 'a6333a3e-0164-44c3-b281-7a577aff287f' # Windows Admin Center Administrator Login ) # Check if required data is available if (-not $script:EntraService.UserMFAStatus) { $findingParams = @{ CheckMetadata = $CheckMetadata Status = 'SKIPPED' StatusExtended = 'Unable to retrieve user MFA registration details - missing permissions' ResourceId = 'N/A' ResourceName = 'User MFA Status' } New-CIEMFinding @findingParams } elseif (-not $script:IAMService -or -not $script:IAMService.RoleAssignments) { $findingParams = @{ CheckMetadata = $CheckMetadata Status = 'SKIPPED' StatusExtended = 'Unable to retrieve IAM role assignments - IAM service not initialized or missing permissions. This check requires cross-reference with Azure Resource Manager role assignments.' ResourceId = 'N/A' ResourceName = 'IAM Role Assignments' } New-CIEMFinding @findingParams } else { # Build MFA status lookup $mfaStatusLookup = @{} foreach ($mfaStatus in $script:EntraService.UserMFAStatus) { $mfaStatusLookup[$mfaStatus.id] = $mfaStatus } # Find users with VM-related role assignments $usersWithVmAccess = @{} foreach ($assignment in $script:IAMService.RoleAssignments) { # Check if this is a VM-related role $roleDefinitionId = $assignment.roleDefinitionId if ($roleDefinitionId) { # Extract the role GUID from the full resource ID $roleGuid = ($roleDefinitionId -split '/')[-1] # Check if it's a VM-related role or if the scope includes VMs $isVmRole = $vmRoleDefinitionIds -contains $roleGuid $isVmScope = $assignment.scope -match '/Microsoft.Compute/virtualMachines/' if ($isVmRole -or $isVmScope) { $principalId = $assignment.principalId $principalType = $assignment.principalType # Only check user principals (not groups or service principals) if ($principalType -eq 'User' -and $principalId) { if (-not $usersWithVmAccess.ContainsKey($principalId)) { $usersWithVmAccess[$principalId] = @{ PrincipalId = $principalId Roles = @() Scopes = @() } } $usersWithVmAccess[$principalId].Roles += $roleGuid $usersWithVmAccess[$principalId].Scopes += $assignment.scope } } } } if ($usersWithVmAccess.Count -eq 0) { $findingParams = @{ CheckMetadata = $CheckMetadata Status = 'PASS' StatusExtended = 'No users found with direct VM access role assignments' ResourceId = 'vm-access-users' ResourceName = 'VM Access Users' } New-CIEMFinding @findingParams } else { # Check MFA status for each user with VM access $usersWithoutMfa = @() $usersWithMfa = @() foreach ($principalId in $usersWithVmAccess.Keys) { $userInfo = $usersWithVmAccess[$principalId] $mfaStatus = $mfaStatusLookup[$principalId] # Check if user has MFA methods registered $hasMfa = $false if ($mfaStatus) { if ($mfaStatus.isMfaRegistered -eq $true) { $hasMfa = $true } elseif ($mfaStatus.methodsRegistered -and $mfaStatus.methodsRegistered.Count -gt 0) { $mfaMethods = $mfaStatus.methodsRegistered | Where-Object { $_ -ne 'password' } if ($mfaMethods.Count -gt 0) { $hasMfa = $true } } } if ($hasMfa) { $usersWithMfa += $userInfo } else { # Try to get user details from MFA status or users list $userName = if ($mfaStatus) { $mfaStatus.userPrincipalName } else { $principalId } $userInfo.UserName = $userName $usersWithoutMfa += $userInfo } } $totalVmUsers = $usersWithVmAccess.Count $withoutMfaCount = $usersWithoutMfa.Count if ($withoutMfaCount -eq 0) { $findingParams = @{ CheckMetadata = $CheckMetadata Status = 'PASS' StatusExtended = "All $totalVmUsers users with VM access have MFA enabled" ResourceId = 'vm-access-users' ResourceName = 'VM Access Users' } New-CIEMFinding @findingParams } else { foreach ($userInfo in $usersWithoutMfa) { $findingParams = @{ CheckMetadata = $CheckMetadata Status = 'FAIL' StatusExtended = "User '$($userInfo.UserName)' has VM access but does not have MFA enabled" ResourceId = $userInfo.PrincipalId ResourceName = $userInfo.UserName } New-CIEMFinding @findingParams } } } } } |