-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #416 from RobbeVandenDaele/main
Proposal to add conditional access gap test
- Loading branch information
Showing
4 changed files
with
245 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
<# | ||
.Synopsis | ||
This function checks if all objects found in policy exclusions are found in policy inclusions. | ||
.Description | ||
Checks for gaps in conditional access policies, by looking for excluded objects which are not specifically inlcuded | ||
in another conditional access policy. Instead of looking at the historical sign-ins to find gaps, we try to spot possibly | ||
overlooked exclusions which do not have a fallback. | ||
Reference: | ||
https://learn.microsoft.com/en-us/entra/identity/monitoring-health/workbook-conditional-access-gap-analyzer | ||
.Example | ||
Test-MtCaGaps | ||
.LINK | ||
https://maester.dev/docs/commands/Test-MtCaGaps | ||
#> | ||
function Get-ObjectDifferences { | ||
[CmdletBinding()] | ||
param ( | ||
[System.Collections.ArrayList]$excludedObjects, | ||
[System.Collections.ArrayList]$includedObjects | ||
) | ||
|
||
# Only get unique values | ||
$excludedObjects = @($excludedObjects | Select-Object -Unique) | ||
$includedObjects = @($includedObjects | Select-Object -Unique) | ||
# Get all the objects that are excluded somewhere but included somewhere else | ||
$excludedObjectsWithFallback = $excludedObjects | Where-Object { | ||
$includedObjects -contains $_ | ||
} | ||
# Get the differences between the two Arrays, so we can find which objects did not have a fallback | ||
$objectDifferences = @($excludedObjects | Where-Object { | ||
$excludedObjectsWithFallback -notcontains $_ | ||
}) | ||
|
||
return $objectDifferences | ||
} | ||
|
||
function Get-RalatedPolicies { | ||
[CmdletBinding()] | ||
param ( | ||
[System.Collections.ArrayList]$Arr, | ||
[String]$ObjName | ||
) | ||
$result = "" | ||
|
||
# Check each policy in the array | ||
foreach ($obj in $Arr) { | ||
# Check if the excluded object is present in the policy | ||
if ($obj.ExcludedObjects -contains $ObjName) { | ||
$result += " - Excluded in policy '$($obj.PolicyName)'`n`n" | ||
} | ||
} | ||
|
||
return $result | ||
} | ||
|
||
function Test-MtCaGaps { | ||
[CmdletBinding()] | ||
[OutputType([bool])] | ||
param () | ||
|
||
$result = $false | ||
$testDescription = "All excluded objects should have a fallback include in another policy" | ||
|
||
# Get the enabled conditional access policies | ||
$policies = Get-MtConditionalAccessPolicy | Where-Object { $_.state -eq "enabled" } | ||
Write-Verbose "Retrieved conditional access policies:`n $policies" | ||
|
||
# Variabes related to users | ||
[System.Collections.ArrayList]$excludedUsers = @() | ||
[System.Collections.ArrayList]$includedUsers = @() | ||
[System.Collections.ArrayList]$differencesUsers = @() | ||
# Variabes related to groups | ||
[System.Collections.ArrayList]$excludedGroups = @() | ||
[System.Collections.ArrayList]$includedGroups = @() | ||
[System.Collections.ArrayList]$differencesGroups = @() | ||
# Variabes related to Roles | ||
[System.Collections.ArrayList]$excludedRoles = @() | ||
[System.Collections.ArrayList]$includedRoles = @() | ||
[System.Collections.ArrayList]$differencesRoles = @() | ||
# Variabes related to Applications | ||
[System.Collections.ArrayList]$excludedApplications = @() | ||
[System.Collections.ArrayList]$includedApplications = @() | ||
[System.Collections.ArrayList]$differencesApplications = @() | ||
# Variabes related to ServicePrincipals | ||
[System.Collections.ArrayList]$excludedServicePrincipals = @() | ||
[System.Collections.ArrayList]$includedServicePrincipals = @() | ||
[System.Collections.ArrayList]$differencesServicePrincipals = @() | ||
# Variabes related to Locations | ||
[System.Collections.ArrayList]$excludedLocations = @() | ||
[System.Collections.ArrayList]$includedLocations = @() | ||
[System.Collections.ArrayList]$differencesLocations = @() | ||
# Variabes related to Platforms | ||
[System.Collections.ArrayList]$excludedPlatforms = @() | ||
[System.Collections.ArrayList]$includedPlatforms = @() | ||
[System.Collections.ArrayList]$differencesPlatforms = @() | ||
# Mapping array | ||
[System.Collections.ArrayList]$mappingArray = @() | ||
|
||
# Get all the objects for all policies | ||
$policies | ForEach-Object { | ||
# Save all interesting objects for later use | ||
$_.Conditions.Users.ExcludeUsers | ForEach-Object { $excludedUsers.Add($_) | Out-Null } | ||
$_.Conditions.Users.IncludeUsers | ForEach-Object { $includedUsers.Add($_) | Out-Null } | ||
$_.Conditions.Users.ExcludeGroups | ForEach-Object { $excludedGroups.Add($_) | Out-Null } | ||
$_.Conditions.Users.IncludeGroups | ForEach-Object { $includedGroups.Add($_) | Out-Null } | ||
$_.Conditions.Users.ExcludeRoles | ForEach-Object { $excludedRoles.Add($_) | Out-Null } | ||
$_.Conditions.Users.IncludeRoles | ForEach-Object { $includedRoles.Add($_) | Out-Null } | ||
$_.Conditions.Applications.ExcludeApplications | ForEach-Object { $excludedApplications.Add($_) | Out-Null } | ||
$_.Conditions.Applications.IncludeApplications | ForEach-Object { $includedApplications.Add($_) | Out-Null } | ||
$_.Conditions.ClientApplications.ExcludeServicePrincipals | ForEach-Object { $excludedServicePrincipals.Add($_) | Out-Null } | ||
$_.Conditions.ClientApplications.IncludeServicePrincipals | ForEach-Object { $includedServicePrincipals.Add($_) | Out-Null } | ||
$_.Conditions.Locations.ExcludeLocations | ForEach-Object { $excludedLocations.Add($_) | Out-Null } | ||
$_.Conditions.Locations.IncludeLocations | ForEach-Object { $includedLocations.Add($_) | Out-Null } | ||
$_.Conditions.Locations.Platforms | ForEach-Object { $excludedPlatforms.Add($_) | Out-Null } | ||
$_.Conditions.Locations.Platforms | ForEach-Object { $includedPlatforms.Add($_) | Out-Null } | ||
|
||
# Create a mapping for each policy with excluded objects | ||
[System.Collections.ArrayList]$allExcluded = $_.Conditions.Users.ExcludeUsers + ` | ||
$_.Conditions.Users.ExcludeGroups + ` | ||
$_.Conditions.Users.ExcludeRoles + ` | ||
$_.Conditions.Applications.ExcludeApplications + ` | ||
$_.Conditions.ClientApplications.ExcludeServicePrincipals + ` | ||
$_.Conditions.Locations.ExcludeLocations + ` | ||
$_.Conditions.Locations.Platforms | ||
# Create the mapping | ||
$mapping = [PSCustomObject]@{ | ||
PolicyName = $_.DisplayName | ||
ExcludedObjects = $allExcluded | ||
} | ||
# Add the mapping to the array and clear variable | ||
$mappingArray += $mapping | ||
Clear-Variable -Name allExcluded | ||
} | ||
Write-Verbose "Created a mapping with all excluded objects for each policy:`n $mapping" | ||
|
||
# Find which objects are excluded without a fallback | ||
[System.Collections.ArrayList]$differencesUsers = @(Get-ObjectDifferences -excludedObjects $excludedUsers -includedObjects $includedUsers) | ||
[System.Collections.ArrayList]$differencesGroups = @(Get-ObjectDifferences -excludedObjects $excludedGroups -includedObjects $includedGroups) | ||
[System.Collections.ArrayList]$differencesRoles = @(Get-ObjectDifferences -excludedObjects $excludedRoles -includedObjects $includedRoles) | ||
[System.Collections.ArrayList]$differencesApplications = @(Get-ObjectDifferences -excludedObjects $excludedApplications -includedObjects $includedApplications) | ||
[System.Collections.ArrayList]$differencesServicePrincipals = @(Get-ObjectDifferences -excludedObjects $excludedServicePrincipals -includedObjects $includedServicePrincipals) | ||
[System.Collections.ArrayList]$differencesLocations = @(Get-ObjectDifferences -excludedObjects $excludedLocations -includedObjects $includedLocations) | ||
[System.Collections.ArrayList]$differencesPlatforms = @(Get-ObjectDifferences -excludedObjects $excludedPlatforms -includedObjects $includedPlatforms) | ||
Write-Host "Finished searching for gaps in policies." | ||
|
||
# Check if all excluded objects have fallbacks | ||
if ( | ||
$differencesUsers.Count -eq 0 ` | ||
-and $differencesGroups.Count -eq 0 ` | ||
-and $differencesRoles.Count -eq 0 ` | ||
-and $differencesApplications.Count -eq 0 ` | ||
-and $differencesServicePrincipals.Count -eq 0 ` | ||
-and $differencesLocations.Count -eq 0 ` | ||
-and $differencesPlatforms.Count -eq 0 ` | ||
) { | ||
$result = $true | ||
$testResult = "All excluded objects seem to have a fallback in other policies." | ||
Write-Verbose "All excluded objects seem to have a fallback in other policies." | ||
} else { | ||
Write-Verbose "Not all excluded objects seem to have a fallback in other policies." | ||
# Add user objects to results | ||
if ($differencesUsers.Count -ne 0) { | ||
$testResult = "The following user objects did not have a fallback:`n`n" | ||
$differencesUsers | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
# Add group objects to results | ||
if ($differencesGroups.Count -ne 0) { | ||
$testResult += "The following group objects did not have a fallback:`n`n" | ||
$differencesGroups | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
# Add role objects to results | ||
if ($differencesRoles.Count -ne 0) { | ||
$testResult += "The following role objects did not have a fallback:`n`n" | ||
$differencesRoles | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
# Add application objects to results | ||
if ($differencesApplications.Count -ne 0) { | ||
$testResult += "The following application objects did not have a fallback:`n`n" | ||
$differencesApplications | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
# Add service principal objects to results | ||
if ($differencesServicePrincipals.Count -ne 0) { | ||
$testResult += "The following service principal objects did not have a fallback:`n`n" | ||
$differencesServicePrincipals | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
# Add location objects to results | ||
if ($differencesLocations.Count -ne 0) { | ||
$testResult += "The following location objects did not have a fallback:`n`n" | ||
$differencesLocations | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
# Add platform objects to results | ||
if ($differencesPlatforms.Count -ne 0) { | ||
$testResult += "The following platform objects did not have a fallback:`n`n" | ||
$differencesPlatforms | ForEach-Object { | ||
$testResult += " - $_`n`n" | ||
$testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ | ||
} | ||
} | ||
} | ||
Add-MtTestResultDetail -Description $testDescription -Result $testResult | ||
|
||
return $result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
title: MT.1036 - All excluded objects should have a fallback include in another policy. | ||
description: Checks for gaps in conditional access policies, by looking for excluded objects which are not specifically inlcuded in another conditional access policy. This way we try to spot possibly overlooked exclusions which do not have a fallback. | ||
slug: /tests/MT.1036 | ||
sidebar_class_name: hidden | ||
--- | ||
|
||
# All excluded objects should have a fallback include in another policy | ||
|
||
## Description | ||
|
||
When exlcuding objects from consitional access policies, you are basically creating gaps into your protection mechanisms. To make sure these gaps are not left open, the excluded objects should be included in another 'fallback' policy. By doing this, you make sure al objects are protected and no gaps exists. | ||
|
||
## How to fix | ||
|
||
Review the excluded objects and their related policies and try to create a fallback policy for them. |