Skip to content

Commit

Permalink
Merge pull request #488 from maester365/tnh-CaInvalidGroups
Browse files Browse the repository at this point in the history
Added check for invalid CA group targeting
  • Loading branch information
merill authored Dec 6, 2024
2 parents a1d52cd + 907c038 commit 357c20a
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 4 deletions.
4 changes: 2 additions & 2 deletions powershell/Maester.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae
'Test-MtCaExclusionForDirectorySyncAccount',
'Test-MtCaLicenseUtilization', 'Test-MtCaMfaForAdmin',
'Test-MtCaMfaForAdminManagement', 'Test-MtCaMfaForAllUsers',
'Test-MtCaGroupsRestricted',
'Test-MtCaGap',
"Test-MtCaGroupsRestricted",
"Test-MtCaGap", "Test-MtCaReferencedGroupsExist",
'Test-MtCaMfaForGuest', 'Test-MtCaMfaForRiskySignIn',
'Test-MtCaRequirePasswordChangeForHighUserRisk',
'Test-MtCaSecureSecurityInfoRegistration', 'Test-MtCisaDiagnosticSettings',
Expand Down
15 changes: 15 additions & 0 deletions powershell/public/Test-MtCaReferencedGroupsExist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
This test checks if there are any Conditional Access policies that target deleted security groups.

This usually happens when a group is deleted but is still referenced in a Conditional Access policy.

Deleted groups in your policy can lead to unexpected gaps. This may result in Conditional Access policies not being applied to the users you intended or the policy not being applied at all.

To fix this issue:

* Open the impacted Conditional access policy.
* If the group is no longer needed, click Save to remove the referenced group from the policy.
* If the group is still needed, update the policy to target a valid group.

<!--- Results --->

%TestResult%
78 changes: 78 additions & 0 deletions powershell/public/Test-MtCaReferencedGroupsExist.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<#
.Synopsis
Checks if any conditional access policies include or exclude groups that have been deleted.
.Description
Security Groups will be used to exclude and include users from Conditional Access Policies.
Assignments are still visible in the policy definition in Microsoft Graph API even the group is deleted.
This test checks if all groups used in Conditional Access Policies still exist and shows invalid or deleted items.
.Example
Test-MtCaReferencedGroupsExist
.LINK
https://maester.dev/docs/commands/Test-MtCaReferencedGroupsExist
#>

Function Test-MtCaReferencedGroupsExist {
[CmdletBinding()]
[OutputType([bool])]
param ()

Write-Verbose "Running Test-MtCaReferencedGroupsExist"
# Execute the test only when PowerShell Core and parallel processing is supported
if ($PSVersionTable.PSEdition -eq 'Core') {

$testDescription = ""
$Policies = Get-MtConditionalAccessPolicy

$Groups = $Policies.conditions.users.includeGroups + $Policies.conditions.users.excludeGroups | Select-Object -Unique

$GroupsWhichNotExist = [System.Collections.Concurrent.ConcurrentBag[psobject]]::new()
$Groups | ForEach-Object -Parallel {
$Group = $_
$NotExistedGroup = $using:GroupsWhichNotExist
$GraphQueryResult = Invoke-MtGraphRequest -RelativeUri "groups/$($Group)" -ApiVersion beta -ErrorVariable GraphErrorResult -ErrorAction SilentlyContinue
if ([string]::IsNullOrEmpty($GraphQueryResult)) {
$NotExistedGroup.Add($Group) | Out-Null
}
}

$result = ($GroupsWhichNotExist | Measure-Object).Count -eq 0

if ( $result ) {
$ResultDescription = "Well done! All Conditional Access policies are targeting active groups."
} else {
$ResultDescription = "These Conditional Access policies are referencing deleted security groups."
$ImpactedCaGroups = "`n`n#### Impacted Conditional Access policies`n`n | Conditional Access policy | Deleted security group | Condition | `n"
$ImpactedCaGroups += "| --- | --- | --- |`n"
}

$GroupsWhichNotExist | Sort-Object | ForEach-Object {
$InvalidGroupId = $_
$ImpactedPolicies = Get-MtConditionalAccessPolicy | Where-Object { $_.conditions.users.includeGroups -contains $InvalidGroupId -or $_.conditions.users.excludeGroups -contains $InvalidGroupId }
foreach ($ImpactedPolicy in $ImpactedPolicies) {
if ($ImpactedPolicy.conditions.users.includeGroups -contains $InvalidGroupId) {
$Condition = "include"
} elseif ($ImpactedPolicy.conditions.users.excludeGroups -contains $InvalidGroupId) {
$Condition = "exclude"
} else {
$Condition = "Unknown"
}
$Policy = (Get-GraphObjectMarkdown -GraphObjects $ImpactedPolicy -GraphObjectType ConditionalAccess -AsPlainTextLink)
$ImpactedCaGroups += "| $($Policy) | $($InvalidGroupId) | $($Condition) | `n"
}
}
$ImpactedCaGroups += "`n`nNote: Names are not available for deleted groups. If the group was deleted in the last 30 days it may be available under [Entra admin centre - Deleted groups](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/GroupsManagementMenuBlade/~/DeletedGroups/menuId/DeletedGroups).`n`n"

$resultMarkdown = $ResultDescription + $ImpactedCaGroups
Add-MtTestResultDetail -Description $testDescription -Result $resultMarkdown
return $result

} else {
Write-Verbose "PowerShell Core not available, skip the test"
# PowerShell Core not available, skip the test
Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "Requires PowerShell 7.x or above. This test uses features that are not available in Windows PowerShell (5.x)."
return $null
}
}
7 changes: 5 additions & 2 deletions tests/Maester/Entra/Test-ConditionalAccessBaseline.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,13 @@
Test-MtCaExclusionForDirectorySyncAccount | Should -Be $true -Because "there is no policy that excludes directory synchronization accounts"
}
It "MT.1035: All security groups assigned to Conditional Access Policies should be protected by RMAU. See https://maester.dev/docs/tests/MT.1035" -Tag "MT.1035" {
Test-MtCaGroupsRestricted | Should -Be $true -Because "there is one or more policy without protection of included or excluded groups"
Test-MtCaGroupsRestricted | Should -Be $true -Because "there are one or more policies without protection of included or excluded groups"
}
It "MT.1036: All excluded objects should have a fallback include in another policy. See https://maester.dev/docs/tests/MT.1036" -Tag "MT.1036", "Warning" {
Test-MtCaGap | Should -Be $true -Because "there is one ore more object excluded without an include fallback in another policy."
Test-MtCaGap | Should -Be $true -Because "there are one or more objects excluded without a corresponding fallback in another policy."
}
It "MT.1038: Conditional Access policies should not include or exclude deleted groups. See https://maester.dev/docs/tests/MT.1038" -Tag "MT.1038", "Warning" {
Test-MtCaReferencedGroupsExist | Should -Be $true -Because "there are one or more policies relying on deleted groups."
}
Context "License utilization" {
It "MT.1022: All users utilizing a P1 license should be licensed. See https://maester.dev/docs/tests/MT.1022" -Tag "MT.1022" {
Expand Down

0 comments on commit 357c20a

Please sign in to comment.