Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bugfix] GR1 V1 MFA reporting for guest user #187

Merged
merged 7 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified psmodules/Check-AllUserMFARequired.zip
Binary file not shown.
Binary file modified psmodules/GR-Common.zip
Binary file not shown.
Binary file modified psmodules/Get-AzureADLicenseType.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions setup/IaC/modules/automationaccount.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ resource module11 'modules' = if (newDeployment || updatePSModules) {
properties: {
contentLink: {
uri: '${ModuleBaseURL}/GR-Common.zip'
version: '1.1.18'
version: '1.1.19'
}
}
}
Expand Down Expand Up @@ -303,7 +303,7 @@ resource module12 'modules' = if (newDeployment || updatePSModules) {
properties: {
contentLink: {
uri: '${ModuleBaseURL}/Check-AllUserMFARequired.zip'
version: '1.0.0'
version: '1.0.1'
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion setup/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"ModuleType": "Builtin",
"Status": "Enabled",
"Required": "True",
"Script": "Check-AllUserMFARequired -ControlName $msgTable.CtrName1 -ItemName $msgTable.allUserAccountsMFACheck -MsgTable $msgTable -ReportTime $ReportTime -itsgcode $vars.itsgcode -FirstBreakGlassUPN $vars.FirstBreakGlassUPN -SecondBreakGlassUPN $vars.SecondBreakGlassUPN",
"Profiles": [1, 2, 3, 4, 5, 6],
"Script": "Check-AllUserMFARequired -ControlName $msgTable.CtrName1 -ItemName $msgTable.allUserAccountsMFACheck -MsgTable $msgTable -ReportTime $ReportTime -itsgcode $vars.itsgcode -FirstBreakGlassUPN $vars.FirstBreakGlassUPN -SecondBreakGlassUPN $vars.SecondBreakGlassUPN -CloudUsageProfiles $cloudUsageProfilesString -ModuleProfiles $ModuleProfilesString",
"secrets": [
{
"Name": "FirstBreakGlassUPN",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
RootModule = 'Check-AllUserMFARequired'

# Version number of this module.
ModuleVersion = '1.0.0'
ModuleVersion = '1.0.1'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ function Check-AllUserMFARequired {
[Parameter(Mandatory=$true)]
[string] $FirstBreakGlassUPN,
[Parameter(Mandatory=$true)]
[string] $SecondBreakGlassUPN
[string] $SecondBreakGlassUPN,
[string]
$CloudUsageProfiles = "3", # Passed as a string
[string] $ModuleProfiles, # Passed as a string
[switch]
$EnableMultiCloudProfiles # New feature flag, default to false
)

[PSCustomObject] $ErrorList = New-Object System.Collections.ArrayList
Expand All @@ -25,7 +30,11 @@ function Check-AllUserMFARequired {
$urlPath = "/users"
try {
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop
# portal
$data = $response.Content
# # localExecution
# $data = $response

if ($null -ne $data -and $null -ne $data.value) {
$users = $data.value | Select-Object userPrincipalName , displayName, givenName, surname, id, mail
}
Expand All @@ -36,106 +45,46 @@ function Check-AllUserMFARequired {
Write-Error "Error: $errorMsg"
}

## *************************##
## ****** Member user ******##
## *************************##
$memberUsers = $users | Where-Object { $_.userPrincipalName -notlike "*#EXT#*" }

# Check all users for MFA
$allUserUPNs = $users.userPrincipalName

# Get member users UPNs
$memberUserList = $memberUsers | Select-Object userPrincipalName, mail
# Exclude the breakglass account UPNs from the list
if ($allUserUPNs -contains $FirstBreakGlassUPN){
$allUserUPNs = $allUserUPNs | Where-Object { $_ -ne $FirstBreakGlassUPN }
if ($memberUserList.userPrincipalName -contains $FirstBreakGlassUPN){
$memberUserList = $memberUserList | Where-Object { $_.userPrincipalName -ne $FirstBreakGlassUPN }
}
if ($allUserUPNs -contains $SecondBreakGlassUPN){
$allUserUPNs = $allUserUPNs | Where-Object { $_ -ne $SecondBreakGlassUPN }
if ($memberUserList.userPrincipalName -contains $SecondBreakGlassUPN){
$memberUserList = $memberUserList | Where-Object { $_.userPrincipalName -ne $SecondBreakGlassUPN }

}
$result = Get-AllUserAuthInformation -allUserList $memberUserList
$memberUserUPNsBadMFA = $result.userUPNsBadMFA
if( !$null -eq $result.ErrorList){
$ErrorList = $ErrorList.Add($result.ErrorList)
}
$userValidMFACounter = $result.userValidMFACounter

$userValidMFACounter = 0
$userUPNsBadMFA = @()

ForEach ($userAccount in $allUserUPNs) {
$urlPath = '/users/' + $userAccount + '/authentication/methods'

try {
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop

}
catch {
$errorMsg = "Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_"
$ErrorList.Add($errorMsg)
Write-Error "Error: $errorMsg"
}

# # To check if MFA is setup for a user, we're checking various authentication methods:
# # 1. #microsoft.graph.microsoftAuthenticatorAuthenticationMethod
# # 2. #microsoft.graph.phoneAuthenticationMethod
# # 3. #microsoft.graph.passwordAuthenticationMethod - not considered for MFA
# # 4. #microsoft.graph.emailAuthenticationMethod - not considered for MFA
# # 5. #microsoft.graph.fido2AuthenticationMethod
# # 6. #microsoft.graph.softwareOathAuthenticationMethod
# # 7. #microsoft.graph.temporaryAccessPassAuthenticationMethod
# # 8. #microsoft.graph.windowsHelloForBusinessAuthenticationMethod

if ($null -ne $response) {
$data = $response.Content
if ($null -ne $data -and $null -ne $data.value) {
$authenticationmethods = $data.value

$authFound = $false
$authCounter = 0
foreach ($authmeth in $authenticationmethods) {

if (($($authmeth.'@odata.type') -eq "#microsoft.graph.phoneAuthenticationMethod") -or `
($($authmeth.'@odata.type') -eq "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod") -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.fido2AuthenticationMethod" ) -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.temporaryAccessPassAuthenticationMethod" ) -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" ) -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.softwareOathAuthenticationMethod" ) ) {

# need to keep track of user's mfa auth count
$authCounter += 1
}
if ($authCounter -ge 1){
$authFound = $true
}
}

if($authFound){
#need to keep track of user account mfa in a counter and compare it with the total user count
$userValidMFACounter += 1
Write-Host "Auth method found for $userAccount"
}
else{
# This message is being used for debugging
Write-Host "$userAccount does not have MFA enabled"
## ***************************##
## ****** External user ******##
## ***************************##
$extUsers = $users | Where-Object { $_.userPrincipalName -like "*#EXT#*" }

$authCounter = 0
# Create an instance of inner list object
$userUPNtemplate = [PSCustomObject]@{
UPN = $userAccount
MFAStatus = $false
}
# Add the list to user accounts MFA list
$userUPNsBadMFA += $userUPNtemplate
}
}
else {
$errorMsg = "No authentication methods data found for $userAccount"
$ErrorList.Add($errorMsg)
# Write-Error "Error: $errorMsg"

# Create an instance of inner list object
$userUPNtemplate = [PSCustomObject]@{
UPN = $userAccount
MFAStatus = $false
}
# Add the list to user accounts MFA list
$userUPNsBadMFA += $userUPNtemplate
}
}
else {
$errorMsg = "Failed to get response from Graph API for $userAccount"
$ErrorList.Add($errorMsg)
Write-Error "Error: $errorMsg"
}
# Get external users UPNs and emails
$extUserList = $extUsers | Select-Object userPrincipalName, mail
$result2 = Get-AllUserAuthInformation -allUserList $extUserList
$extUserUPNsBadMFA = $result2.userUPNsBadMFA
if( !$null -eq $result2.ErrorList){
$ErrorList = $ErrorList.Add($result2.ErrorList)
}
# combined list
$userValidMFACounter = $userValidMFACounter + $result2.userValidMFACounter
$userUPNsBadMFA = $memberUserUPNsBadMFA + $extUserUPNsBadMFA

# Condition: all users are MFA enabled
if($userValidMFACounter -eq $allUserUPNs.Count) {
Expand Down Expand Up @@ -165,6 +114,23 @@ function Check-AllUserMFARequired {
ReportTime = $ReportTime
itsgcode = $itsgcode
}

# Conditionally add the Profile field based on the feature flag
if ($EnableMultiCloudProfiles) {
$result = Get-EvaluationProfile -CloudUsageProfiles $CloudUsageProfiles -ModuleProfiles $ModuleProfiles
if ($result -is [int]) {
Write-Output "Valid profile returned: $result"
$PsObject | Add-Member -MemberType NoteProperty -Name "Profile" -Value $result
} elseif ($result -is [hashtable] -and $result.Status -eq "Error") {
Write-Error "Error occurred: $($result.Message)"
$PsObject.ComplianceStatus = "Not Applicable"
Errorslist.Add($result.Message)
} else {
Write-Error "Unexpected result type: $($result.GetType().Name), Value: $result"
}
}


$moduleOutput= [PSCustomObject]@{
ComplianceResults = $PsObject
Errors=$ErrorList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function Get-ADLicenseType {
}


$moduleOutput= [PSCustomObject]@{
$moduleOutput= [PSCustomObject]@{
ComplianceResults = $PsObject
Errors=$ErrorList
AdditionalResults = $AdditionalResults
Expand Down
131 changes: 129 additions & 2 deletions src/Guardrails-Common/GR-Common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -809,11 +809,11 @@ function Invoke-GraphQuery {
@{
Content = $response.Content | ConvertFrom-Json
StatusCode = $response.StatusCode

}

}



# Function to add other possible file extension(s) to the module file names
function add-documentFileExtensions {
param (
Expand Down Expand Up @@ -841,6 +841,133 @@ function add-documentFileExtensions {
}



function Get-AllUserAuthInformation{
param (
[Parameter(Mandatory = $true)]
[array]$allUserList
)
[PSCustomObject] $ErrorList = New-Object System.Collections.ArrayList
$userValidMFACounter = 0
$userUPNsBadMFA = @()

ForEach ($user in $allUserList) {
$userAccount = $user.userPrincipalName

if($userAccount -like "*#EXT#*"){
# for guest accounts
$userEmail = $user.mail
if(!$null -eq $userEmail){
$urlPath = '/users/' + $userEmail + '/authentication/methods'
}else{
Write-Host "userEmail is null for $userAccount"
$extractedEmail = (($userAccount -split '#')[0]) -replace '_', '@'
$urlPath = '/users/' + $extractedEmail + '/authentication/methods'
}

}else{
# for member accounts
$urlPath = '/users/' + $userAccount + '/authentication/methods'
}

try {
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop

}
catch {
$errorMsg = "Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_"
$ErrorList.Add($errorMsg)
Write-Error "Error: $errorMsg"
}

# # To check if MFA is setup for a user, we're checking various authentication methods:
# # 1. #microsoft.graph.microsoftAuthenticatorAuthenticationMethod
# # 2. #microsoft.graph.phoneAuthenticationMethod
# # 3. #microsoft.graph.passwordAuthenticationMethod - not considered for MFA
# # 4. #microsoft.graph.emailAuthenticationMethod - not considered for MFA
# # 5. #microsoft.graph.fido2AuthenticationMethod
# # 6. #microsoft.graph.softwareOathAuthenticationMethod
# # 7. #microsoft.graph.temporaryAccessPassAuthenticationMethod
# # 8. #microsoft.graph.windowsHelloForBusinessAuthenticationMethod

if ($null -ne $response) {
# portal
$data = $response.Content
# # localExecution
# $data = $response
if ($null -ne $data -and $null -ne $data.value) {
$authenticationmethods = $data.value

$authFound = $false
$authCounter = 0
foreach ($authmeth in $authenticationmethods) {

if (($($authmeth.'@odata.type') -eq "#microsoft.graph.phoneAuthenticationMethod") -or `
($($authmeth.'@odata.type') -eq "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod") -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.fido2AuthenticationMethod" ) -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.temporaryAccessPassAuthenticationMethod" ) -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" ) -or`
($($authmeth.'@odata.type') -eq "#microsoft.graph.softwareOathAuthenticationMethod" ) ) {

# need to keep track of user's mfa auth count
$authCounter += 1
}
}
if ($authCounter -ge 1){
$authFound = $true
}

if($authFound){
#need to keep track of user account mfa in a counter and compare it with the total user count
$userValidMFACounter += 1
Write-Host "Auth method found for $userAccount"
}
else{
# This message is being used for debugging
Write-Host "$userAccount does not have MFA enabled"

$authCounter = 0
# Create an instance of inner list object
$userUPNtemplate = [PSCustomObject]@{
UPN = $userAccount
MFAStatus = $false
}
# Add the list to user accounts MFA list
$userUPNsBadMFA += $userUPNtemplate
}
}
else {
$errorMsg = "No authentication methods data found for $userAccount"
$ErrorList.Add($errorMsg)
# Write-Error "Error: $errorMsg"

# Create an instance of inner list object
$userUPNtemplate = [PSCustomObject]@{
UPN = $userAccount
MFAStatus = $false
}
# Add the list to user accounts MFA list
$userUPNsBadMFA += $userUPNtemplate
}
}
else {
$errorMsg = "Failed to get response from Graph API for $userAccount"
$ErrorList.Add($errorMsg)
Write-Error "Error: $errorMsg"
}
}

$PsObject = [PSCustomObject]@{
userUPNsBadMFA = $userUPNsBadMFA
ErrorList = $ErrorList
userValidMFACounter = $userValidMFACounter
}

return $PsObject

}


function Get-EvaluationProfile {
param (
[Parameter(Mandatory = $true)]
Expand Down
Loading