Checks/Azure/Test-EntraNonPrivilegedUserHasMfa.ps1
|
function Test-EntraNonPrivilegedUserHasMfa { <# .SYNOPSIS Tests if all non-privileged users have MFA enabled. .DESCRIPTION This check verifies that users who are not assigned to any directory roles (non-privileged users) have MFA registered. MFA provides additional security by requiring a second form of authentication. .PARAMETER CheckMetadata Hashtable containing check metadata including id and severity. .EXAMPLE Test-EntraNonPrivilegedUserHasMfa -CheckMetadata $metadata #> [CmdletBinding()] [OutputType([CIEMScanResult[]])] param( [Parameter(Mandatory)] [hashtable]$CheckMetadata ) $ErrorActionPreference = 'Stop' # Check if required data is available if (-not $script:EntraService.Users) { [CIEMScanResult]::Create( $CheckMetadata, 'SKIPPED', 'Unable to retrieve users - missing permissions', 'N/A', 'Users' ) } elseif (-not $script:EntraService.UserMFAStatus) { [CIEMScanResult]::Create( $CheckMetadata, 'SKIPPED', 'Unable to retrieve user MFA registration details - Azure AD Premium P1/P2 license required', 'N/A', 'User MFA Status' ) } else { # Build a set of privileged user IDs (users in any directory role) $privilegedUserIds = @{} if ($script:EntraService.DirectoryRoleMembers) { foreach ($roleId in $script:EntraService.DirectoryRoleMembers.Keys) { $members = $script:EntraService.DirectoryRoleMembers[$roleId] if ($members) { foreach ($member in $members) { if ($member.id) { $privilegedUserIds[$member.id] = $true } } } } } # Build MFA status lookup $mfaStatusLookup = @{} foreach ($mfaStatus in $script:EntraService.UserMFAStatus) { $mfaStatusLookup[$mfaStatus.id] = $mfaStatus } # Check non-privileged users for MFA $nonPrivilegedUsers = $script:EntraService.Users | Where-Object { -not $privilegedUserIds.ContainsKey($_.id) -and $_.accountEnabled -eq $true -and $_.userType -eq 'Member' } $usersWithoutMfa = @() $usersWithMfa = @() foreach ($user in $nonPrivilegedUsers) { $mfaStatus = $mfaStatusLookup[$user.id] # Check if user has MFA methods registered $hasMfa = $false if ($mfaStatus) { # Check isMfaRegistered property or methodsRegistered array if ($mfaStatus.isMfaRegistered -eq $true) { $hasMfa = $true } elseif ($mfaStatus.methodsRegistered -and $mfaStatus.methodsRegistered.Count -gt 0) { # Check for actual MFA methods (not just password) $mfaMethods = $mfaStatus.methodsRegistered | Where-Object { $_ -ne 'password' } if ($mfaMethods.Count -gt 0) { $hasMfa = $true } } } if ($hasMfa) { $usersWithMfa += $user } else { $usersWithoutMfa += $user } } $totalNonPrivileged = $nonPrivilegedUsers.Count $withoutMfaCount = $usersWithoutMfa.Count if ($withoutMfaCount -eq 0 -and $totalNonPrivileged -gt 0) { [CIEMScanResult]::Create( $CheckMetadata, 'PASS', "All $totalNonPrivileged non-privileged users have MFA enabled", 'non-privileged-users', 'Non-Privileged Users' ) } elseif ($totalNonPrivileged -eq 0) { [CIEMScanResult]::Create( $CheckMetadata, 'PASS', 'No non-privileged users found to check', 'non-privileged-users', 'Non-Privileged Users' ) } else { # Report individual users without MFA (limit to first 10 for readability) $displayLimit = 10 $usersToDisplay = $usersWithoutMfa | Select-Object -First $displayLimit foreach ($user in $usersToDisplay) { [CIEMScanResult]::Create( $CheckMetadata, 'FAIL', "Non-privileged user '$($user.displayName)' ($($user.userPrincipalName)) does not have MFA enabled", $user.id, $user.displayName ) } if ($withoutMfaCount -gt $displayLimit) { [CIEMScanResult]::Create( $CheckMetadata, 'FAIL', "... and $($withoutMfaCount - $displayLimit) additional non-privileged users without MFA (total: $withoutMfaCount out of $totalNonPrivileged)", 'non-privileged-users-summary', 'Non-Privileged Users Summary' ) } } } } |