From b9d173faeacf61590903ae94ed637d838962a787 Mon Sep 17 00:00:00 2001 From: Snozz Date: Fri, 9 Aug 2024 19:57:39 -0700 Subject: [PATCH 01/12] First CIS test --- powershell/Maester.psd1 | 1 + powershell/public/cis/Test-MtCisCloudAdmin.md | 26 +++++++ .../public/cis/Test-MtCisCloudAdmin.ps1 | 72 +++++++++++++++++++ tests/cis/Test-MtCisCloudAdmin.Tests.ps1 | 10 +++ website/docs/tests/cis/readme.md | 26 +++++++ 5 files changed, 135 insertions(+) create mode 100644 powershell/public/cis/Test-MtCisCloudAdmin.md create mode 100644 powershell/public/cis/Test-MtCisCloudAdmin.ps1 create mode 100644 tests/cis/Test-MtCisCloudAdmin.Tests.ps1 create mode 100644 website/docs/tests/cis/readme.md diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index bdd3636a..68839d71 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -121,6 +121,7 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCisaDmarcRecordExist', 'Test-MtCisaDmarcRecordReject', 'Test-MtCisaDmarcAggregateCisa', 'Test-MtCisaDmarcReport', 'Test-MtCisaDlp', + 'Test-MtCisCloudAdmin', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtEidscaControl', diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.md b/powershell/public/cis/Test-MtCisCloudAdmin.md new file mode 100644 index 00000000..e60b5853 --- /dev/null +++ b/powershell/public/cis/Test-MtCisCloudAdmin.md @@ -0,0 +1,26 @@ +1.1.1 (L1) Ensure Administrative accounts are separate and cloud-only + +Administrative accounts are special privileged accounts that could have varying levels of access to data, users, and settings. Regular user accounts should never be utilized for administrative tasks and care should be taken, in the case of a hybrid environment, to keep Administrative accounts separated from on-prem accounts. Administrative accounts should not have applications assigned so that they have no access to potentially vulnerable services (EX. email, Teams, SharePoint, etc.) and only access to perform tasks as needed for administrative purposes. + +#### Remediation action: + +To created licensed, separate Administrative accounts for Administrative users: + +1. Navigate to **Microsoft 365 admin center**. +2. Click to expand **Users** select **Active users** +3. Click **Add a user**. +4. Fill out the appropriate fields for Name, user, etc. +5. When prompted to assign licenses select as needed **Microsoft Entra ID P1** or +**Microsoft Entra ID P2**, then click **Next**. +6. Under the **Option settings** screen you may choose from several types of +Administrative access roles. Choose **Admin center access** followed by the +appropriate role then click **Next**. +7. Select **Finish adding**. + +#### Related links + +* [Microsoft 365 Admin Center](https://admin.microsoft.com) +* [CIS Microsoft 365 Foundations Benchmark v3.1.0 - Page 16](https://www.cisecurity.org/benchmark/microsoft_365) + + +%TestResult% \ No newline at end of file diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 new file mode 100644 index 00000000..9c010b6d --- /dev/null +++ b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Checks if Global Admins are cloud users + +.DESCRIPTION + Ensure Administrative accounts are separate and cloud-only + +.EXAMPLE + Test-MtCisCloudAdmin + + Returns true if no global admins are hybrid sync + +.LINK + https://maester.dev/docs/commands/Test-MtCisCloudAdmin +#> +function Test-MtCisCloudAdmin { + [CmdletBinding()] + [OutputType([bool])] + param() + + $role = Get-MtRole | Where-Object {` + $_.id -eq "62e90394-69f5-4237-9190-012177145e10" } # Global Administrator + + $assignments = Get-MtRoleMember -roleId $role.id + + $globalAdministrators = $assignments | Where-Object {` + $_.'@odata.type' -eq "#microsoft.graph.user" + } + + $userIds = @($globalAdministrators.Id) + + $users = Invoke-MtGraphRequest -RelativeUri "users" -UniqueId $userIds -Select id,displayName,onPremisesSyncEnabled + + $result = $users | Where-Object {` + $_.onPremisesSyncEnabled -eq $true + } + + $testResult = ($result|Measure-Object).Count -eq 0 + + $sortSplat = @{ + Property = @( + @{ + Expression = "onPremisesSyncEnabled" + Descending = $true + }, + @{ + Expression = "displayName" + } + ) + } + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant has no hybrid Global Administrators:`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant has 1 or more hybrid Global Administrators:`n`n%TestResult%" + } + + $resultMd = "| Display Name | Cloud Only |`n" + $resultMd += "| --- | --- |`n" + foreach($item in $users | Sort-Object @sortSplat){ + $itemResult = "❌ Fail" + if($item.id -notin $result.id){ + $itemResult = "✅ Pass" + } + $resultMd += "| $($item.displayName) | $($itemResult) |`n" + } + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $resultMd + + Add-MtTestResultDetail -Result $testResultMarkdown + + return $testResult +} \ No newline at end of file diff --git a/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 new file mode 100644 index 00000000..e7eb86ab --- /dev/null +++ b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 @@ -0,0 +1,10 @@ +Describe "CIS" -Tag "CIS 1.1.1", "CIS E3 Level 1", "CIS E3", "CIS", "Security", "All" { + It "CIS 1.1.1: Ensure Administrative accounts are separate and cloud-only" { + + $result = Test-MtCisCloudAdmin + + if($null -ne $result) { + $result | Should -Be $true -Because "admin accounts are separate and cloud-only" + } + } +} \ No newline at end of file diff --git a/website/docs/tests/cis/readme.md b/website/docs/tests/cis/readme.md new file mode 100644 index 00000000..267e1bc6 --- /dev/null +++ b/website/docs/tests/cis/readme.md @@ -0,0 +1,26 @@ +--- +id: overview +title: CIS Microsoft 365 Foundations Benchmark Tests +sidebar_label: 🏢 CIS Overview +description: Implementation of CIS Microsoft 365 Foundations Benchmark Controls +--- + +# CIS Microsoft 365 Foundations Benchmark + +## Overview + +The tests in this section verifies that a Micorosft 365 tenant's configuration conforms to the [CIS Microsoft 365 Foundations Benchmark](https://www.cisecurity.org/benchmark/microsoft_365) recommendations (v3.1.0). + +The CIS published material is shared for these tests as it aligns with their licensing of [CC BY-NC-SA 4.0](https://www.cisecurity.org/terms-and-conditions-table-of-contents). + +## Connecting to Azure, Exchange and other services + +In order to run all the CIS tests, you need to install and connect to the Azure and Exchange Online modules. + +See the [Installation guide](/docs/installation#optional-modules-and-permissions) for more information. + +## Tests + +| Cmdlet Name | CIS Recommendation ID | +| - | - | +| Test-MtCisCloudAdmin | CIS 1.1.1: Ensure Administrative accounts are separate and cloud-only | \ No newline at end of file From 8d6de95c9f50ea722be4f1fc849356a7b40c472e Mon Sep 17 00:00:00 2001 From: Snozz Date: Fri, 9 Aug 2024 20:08:14 -0700 Subject: [PATCH 02/12] add verbose --- powershell/public/cis/Test-MtCisCloudAdmin.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 index 9c010b6d..8bcd41c6 100644 --- a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 +++ b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 @@ -18,19 +18,24 @@ function Test-MtCisCloudAdmin { [OutputType([bool])] param() + Write-Verbose "Getting Global Admin role" $role = Get-MtRole | Where-Object {` $_.id -eq "62e90394-69f5-4237-9190-012177145e10" } # Global Administrator + Write-Verbose "Getting role members" $assignments = Get-MtRoleMember -roleId $role.id + Write-Verbose "Filtering for users" $globalAdministrators = $assignments | Where-Object {` $_.'@odata.type' -eq "#microsoft.graph.user" } $userIds = @($globalAdministrators.Id) + Write-Verbose "Requesting users onPremisesSyncEnabled property" $users = Invoke-MtGraphRequest -RelativeUri "users" -UniqueId $userIds -Select id,displayName,onPremisesSyncEnabled + Write-Verbose "Filtering users for onPremisesSyncEnabled" $result = $users | Where-Object {` $_.onPremisesSyncEnabled -eq $true } From a5165f7a75438136ebce918231ef9c05434b00f4 Mon Sep 17 00:00:00 2001 From: Snozz Date: Fri, 9 Aug 2024 19:57:39 -0700 Subject: [PATCH 03/12] First CIS test --- powershell/public/cis/Test-MtCisCloudAdmin.md | 26 +++++++ .../public/cis/Test-MtCisCloudAdmin.ps1 | 72 +++++++++++++++++++ tests/cis/Test-MtCisCloudAdmin.Tests.ps1 | 10 +++ website/docs/tests/cis/readme.md | 26 +++++++ 4 files changed, 134 insertions(+) create mode 100644 powershell/public/cis/Test-MtCisCloudAdmin.md create mode 100644 powershell/public/cis/Test-MtCisCloudAdmin.ps1 create mode 100644 tests/cis/Test-MtCisCloudAdmin.Tests.ps1 create mode 100644 website/docs/tests/cis/readme.md diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.md b/powershell/public/cis/Test-MtCisCloudAdmin.md new file mode 100644 index 00000000..e60b5853 --- /dev/null +++ b/powershell/public/cis/Test-MtCisCloudAdmin.md @@ -0,0 +1,26 @@ +1.1.1 (L1) Ensure Administrative accounts are separate and cloud-only + +Administrative accounts are special privileged accounts that could have varying levels of access to data, users, and settings. Regular user accounts should never be utilized for administrative tasks and care should be taken, in the case of a hybrid environment, to keep Administrative accounts separated from on-prem accounts. Administrative accounts should not have applications assigned so that they have no access to potentially vulnerable services (EX. email, Teams, SharePoint, etc.) and only access to perform tasks as needed for administrative purposes. + +#### Remediation action: + +To created licensed, separate Administrative accounts for Administrative users: + +1. Navigate to **Microsoft 365 admin center**. +2. Click to expand **Users** select **Active users** +3. Click **Add a user**. +4. Fill out the appropriate fields for Name, user, etc. +5. When prompted to assign licenses select as needed **Microsoft Entra ID P1** or +**Microsoft Entra ID P2**, then click **Next**. +6. Under the **Option settings** screen you may choose from several types of +Administrative access roles. Choose **Admin center access** followed by the +appropriate role then click **Next**. +7. Select **Finish adding**. + +#### Related links + +* [Microsoft 365 Admin Center](https://admin.microsoft.com) +* [CIS Microsoft 365 Foundations Benchmark v3.1.0 - Page 16](https://www.cisecurity.org/benchmark/microsoft_365) + + +%TestResult% \ No newline at end of file diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 new file mode 100644 index 00000000..9c010b6d --- /dev/null +++ b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Checks if Global Admins are cloud users + +.DESCRIPTION + Ensure Administrative accounts are separate and cloud-only + +.EXAMPLE + Test-MtCisCloudAdmin + + Returns true if no global admins are hybrid sync + +.LINK + https://maester.dev/docs/commands/Test-MtCisCloudAdmin +#> +function Test-MtCisCloudAdmin { + [CmdletBinding()] + [OutputType([bool])] + param() + + $role = Get-MtRole | Where-Object {` + $_.id -eq "62e90394-69f5-4237-9190-012177145e10" } # Global Administrator + + $assignments = Get-MtRoleMember -roleId $role.id + + $globalAdministrators = $assignments | Where-Object {` + $_.'@odata.type' -eq "#microsoft.graph.user" + } + + $userIds = @($globalAdministrators.Id) + + $users = Invoke-MtGraphRequest -RelativeUri "users" -UniqueId $userIds -Select id,displayName,onPremisesSyncEnabled + + $result = $users | Where-Object {` + $_.onPremisesSyncEnabled -eq $true + } + + $testResult = ($result|Measure-Object).Count -eq 0 + + $sortSplat = @{ + Property = @( + @{ + Expression = "onPremisesSyncEnabled" + Descending = $true + }, + @{ + Expression = "displayName" + } + ) + } + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant has no hybrid Global Administrators:`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant has 1 or more hybrid Global Administrators:`n`n%TestResult%" + } + + $resultMd = "| Display Name | Cloud Only |`n" + $resultMd += "| --- | --- |`n" + foreach($item in $users | Sort-Object @sortSplat){ + $itemResult = "❌ Fail" + if($item.id -notin $result.id){ + $itemResult = "✅ Pass" + } + $resultMd += "| $($item.displayName) | $($itemResult) |`n" + } + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $resultMd + + Add-MtTestResultDetail -Result $testResultMarkdown + + return $testResult +} \ No newline at end of file diff --git a/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 new file mode 100644 index 00000000..e7eb86ab --- /dev/null +++ b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 @@ -0,0 +1,10 @@ +Describe "CIS" -Tag "CIS 1.1.1", "CIS E3 Level 1", "CIS E3", "CIS", "Security", "All" { + It "CIS 1.1.1: Ensure Administrative accounts are separate and cloud-only" { + + $result = Test-MtCisCloudAdmin + + if($null -ne $result) { + $result | Should -Be $true -Because "admin accounts are separate and cloud-only" + } + } +} \ No newline at end of file diff --git a/website/docs/tests/cis/readme.md b/website/docs/tests/cis/readme.md new file mode 100644 index 00000000..267e1bc6 --- /dev/null +++ b/website/docs/tests/cis/readme.md @@ -0,0 +1,26 @@ +--- +id: overview +title: CIS Microsoft 365 Foundations Benchmark Tests +sidebar_label: 🏢 CIS Overview +description: Implementation of CIS Microsoft 365 Foundations Benchmark Controls +--- + +# CIS Microsoft 365 Foundations Benchmark + +## Overview + +The tests in this section verifies that a Micorosft 365 tenant's configuration conforms to the [CIS Microsoft 365 Foundations Benchmark](https://www.cisecurity.org/benchmark/microsoft_365) recommendations (v3.1.0). + +The CIS published material is shared for these tests as it aligns with their licensing of [CC BY-NC-SA 4.0](https://www.cisecurity.org/terms-and-conditions-table-of-contents). + +## Connecting to Azure, Exchange and other services + +In order to run all the CIS tests, you need to install and connect to the Azure and Exchange Online modules. + +See the [Installation guide](/docs/installation#optional-modules-and-permissions) for more information. + +## Tests + +| Cmdlet Name | CIS Recommendation ID | +| - | - | +| Test-MtCisCloudAdmin | CIS 1.1.1: Ensure Administrative accounts are separate and cloud-only | \ No newline at end of file From 3eb471dff5f2f5a1305dee4477cc8706ed105b54 Mon Sep 17 00:00:00 2001 From: Snozz Date: Fri, 9 Aug 2024 20:08:14 -0700 Subject: [PATCH 04/12] add verbose --- powershell/public/cis/Test-MtCisCloudAdmin.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 index 9c010b6d..8bcd41c6 100644 --- a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 +++ b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 @@ -18,19 +18,24 @@ function Test-MtCisCloudAdmin { [OutputType([bool])] param() + Write-Verbose "Getting Global Admin role" $role = Get-MtRole | Where-Object {` $_.id -eq "62e90394-69f5-4237-9190-012177145e10" } # Global Administrator + Write-Verbose "Getting role members" $assignments = Get-MtRoleMember -roleId $role.id + Write-Verbose "Filtering for users" $globalAdministrators = $assignments | Where-Object {` $_.'@odata.type' -eq "#microsoft.graph.user" } $userIds = @($globalAdministrators.Id) + Write-Verbose "Requesting users onPremisesSyncEnabled property" $users = Invoke-MtGraphRequest -RelativeUri "users" -UniqueId $userIds -Select id,displayName,onPremisesSyncEnabled + Write-Verbose "Filtering users for onPremisesSyncEnabled" $result = $users | Where-Object {` $_.onPremisesSyncEnabled -eq $true } From e677b0728d98761594eadadd41d68302b7766014 Mon Sep 17 00:00:00 2001 From: Snozz Date: Fri, 23 Aug 2024 17:23:24 -0700 Subject: [PATCH 05/12] Added version refs --- powershell/Maester.psd1 | 2 +- powershell/public/cis/Test-MtCisCloudAdmin.ps1 | 1 + tests/cis/Test-MtCisCloudAdmin.Tests.ps1 | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index d7ac4d0b..1348c1c0 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -126,7 +126,7 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCisaAttachmentFileType', 'Test-MtCisaEmailFilterAlternative', 'Test-MtCisaBlockExecutable', 'Test-MtCisaMalwareAction', 'Test-MtCisaMalwareZap', 'Test-MtCisaImpersonation', 'Test-MtCisaImpersonationTip', 'Test-MtCisaMailboxIntelligence', - 'Get-MtExo', 'Clear-MtExoCache', + 'Get-MtExo', 'Clear-MtExoCache', 'Test-MtCisCloudAdmin', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtEidscaControl', diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 index 8bcd41c6..3de3ef88 100644 --- a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 +++ b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 @@ -4,6 +4,7 @@ .DESCRIPTION Ensure Administrative accounts are separate and cloud-only + CIS Microsoft 365 Foundations Benchmark v3.1.0 .EXAMPLE Test-MtCisCloudAdmin diff --git a/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 index e7eb86ab..2d09286b 100644 --- a/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 +++ b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 @@ -1,4 +1,4 @@ -Describe "CIS" -Tag "CIS 1.1.1", "CIS E3 Level 1", "CIS E3", "CIS", "Security", "All" { +Describe "CIS" -Tag "CIS 1.1.1", "CIS E3 Level 1", "CIS E3", "CIS", "Security", "All", "CIS M365 v3.1.0" { It "CIS 1.1.1: Ensure Administrative accounts are separate and cloud-only" { $result = Test-MtCisCloudAdmin From d70d3eee98789b1a08d0a1169b4676da9593e993 Mon Sep 17 00:00:00 2001 From: Snozz Date: Mon, 26 Aug 2024 15:40:14 -0700 Subject: [PATCH 06/12] rebase --- powershell/Maester.psd1 | 1 + 1 file changed, 1 insertion(+) diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index d7ac4d0b..11fef268 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -127,6 +127,7 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCisaBlockExecutable', 'Test-MtCisaMalwareAction', 'Test-MtCisaMalwareZap', 'Test-MtCisaImpersonation', 'Test-MtCisaImpersonationTip', 'Test-MtCisaMailboxIntelligence', 'Get-MtExo', 'Clear-MtExoCache', + 'Test-MtCisCloudAdmin', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtEidscaControl', From 6596496226d18406bbc001dd119c2139dde85a2f Mon Sep 17 00:00:00 2001 From: Snozz Date: Fri, 23 Aug 2024 17:23:24 -0700 Subject: [PATCH 07/12] Added version refs --- powershell/public/cis/Test-MtCisCloudAdmin.ps1 | 1 + tests/cis/Test-MtCisCloudAdmin.Tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 index 8bcd41c6..3de3ef88 100644 --- a/powershell/public/cis/Test-MtCisCloudAdmin.ps1 +++ b/powershell/public/cis/Test-MtCisCloudAdmin.ps1 @@ -4,6 +4,7 @@ .DESCRIPTION Ensure Administrative accounts are separate and cloud-only + CIS Microsoft 365 Foundations Benchmark v3.1.0 .EXAMPLE Test-MtCisCloudAdmin diff --git a/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 index e7eb86ab..2d09286b 100644 --- a/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 +++ b/tests/cis/Test-MtCisCloudAdmin.Tests.ps1 @@ -1,4 +1,4 @@ -Describe "CIS" -Tag "CIS 1.1.1", "CIS E3 Level 1", "CIS E3", "CIS", "Security", "All" { +Describe "CIS" -Tag "CIS 1.1.1", "CIS E3 Level 1", "CIS E3", "CIS", "Security", "All", "CIS M365 v3.1.0" { It "CIS 1.1.1: Ensure Administrative accounts are separate and cloud-only" { $result = Test-MtCisCloudAdmin From 3d4602dbde35465e79f54ecaa2ecaeab71617881 Mon Sep 17 00:00:00 2001 From: Snozz Date: Mon, 26 Aug 2024 15:45:42 -0700 Subject: [PATCH 08/12] Fix rebase merge issue --- powershell/Maester.psd1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index 1348c1c0..99d13797 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -126,6 +126,10 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCisaAttachmentFileType', 'Test-MtCisaEmailFilterAlternative', 'Test-MtCisaBlockExecutable', 'Test-MtCisaMalwareAction', 'Test-MtCisaMalwareZap', 'Test-MtCisaImpersonation', 'Test-MtCisaImpersonationTip', 'Test-MtCisaMailboxIntelligence', + 'Test-MtCisaSpamFilter', 'Test-MtCisaSpamAction', 'Test-MtCisaSpamBypass', + 'Test-MtCisaSpamAlternative', 'Test-MtCisaSafeLink', 'Test-MtCisaSafeLinkDownloadScan', + 'Test-MtCisaSafeLinkClickTracking', 'Test-MtCisaExoAlert', 'Test-MtCisaExoAlertSiem', + 'Test-MtCisaAuditLog', 'Test-MtCisaAuditLogPremium', 'Test-MtCisaAuditLogRetention', 'Get-MtExo', 'Clear-MtExoCache', 'Test-MtCisCloudAdmin', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', From d67b524e7d0ad88712e1a7e4f14440cac3015341 Mon Sep 17 00:00:00 2001 From: Snozz Date: Mon, 26 Aug 2024 15:46:34 -0700 Subject: [PATCH 09/12] git novice over here --- powershell/Maester.psd1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index 99d13797..70a66106 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -130,7 +130,8 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCisaSpamAlternative', 'Test-MtCisaSafeLink', 'Test-MtCisaSafeLinkDownloadScan', 'Test-MtCisaSafeLinkClickTracking', 'Test-MtCisaExoAlert', 'Test-MtCisaExoAlertSiem', 'Test-MtCisaAuditLog', 'Test-MtCisaAuditLogPremium', 'Test-MtCisaAuditLogRetention', - 'Get-MtExo', 'Clear-MtExoCache', 'Test-MtCisCloudAdmin', + 'Get-MtExo', 'Clear-MtExoCache', + 'Test-MtCisCloudAdmin', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtEidscaControl', From 888dc377c71647cffd789a3e103ed35cfed7cc72 Mon Sep 17 00:00:00 2001 From: Snozz Date: Mon, 26 Aug 2024 15:53:46 -0700 Subject: [PATCH 10/12] Fix #416 --- powershell/Maester.psd1 | 2 +- .../{Test-MtCaGaps.ps1 => Test-MtCaGap.ps1} | 42 ++++++++++--------- .../Test-ConditionalAccessBaseline.Tests.ps1 | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) rename powershell/public/{Test-MtCaGaps.ps1 => Test-MtCaGap.ps1} (87%) diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index 70a66106..5fc274a3 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -95,7 +95,7 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCaLicenseUtilization', 'Test-MtCaMfaForAdmin', 'Test-MtCaMfaForAdminManagement', 'Test-MtCaMfaForAllUsers', "Test-MtCaGroupsRestricted", - "Test-MtCaGaps", + "Test-MtCaGap", 'Test-MtCaMfaForGuest', 'Test-MtCaMfaForRiskySignIn', 'Test-MtCaRequirePasswordChangeForHighUserRisk', 'Test-MtCaSecureSecurityInfoRegistration', 'Test-MtCisaDiagnosticSettings', diff --git a/powershell/public/Test-MtCaGaps.ps1 b/powershell/public/Test-MtCaGap.ps1 similarity index 87% rename from powershell/public/Test-MtCaGaps.ps1 rename to powershell/public/Test-MtCaGap.ps1 index c67dfa6b..e0d95a9e 100644 --- a/powershell/public/Test-MtCaGaps.ps1 +++ b/powershell/public/Test-MtCaGap.ps1 @@ -11,13 +11,14 @@ https://learn.microsoft.com/en-us/entra/identity/monitoring-health/workbook-conditional-access-gap-analyzer .Example - Test-MtCaGaps + Test-MtCaGap .LINK - https://maester.dev/docs/commands/Test-MtCaGaps + https://maester.dev/docs/commands/Test-MtCaGap #> -function Get-ObjectDifferences { +function Get-ObjectDifference { [CmdletBinding()] + [OutputType([object[]])] param ( [System.Collections.ArrayList]$excludedObjects, [System.Collections.ArrayList]$includedObjects @@ -38,8 +39,9 @@ function Get-ObjectDifferences { return $objectDifferences } -function Get-RalatedPolicies { +function Get-RelatedPolicies { [CmdletBinding()] + [OutputType([string])] param ( [System.Collections.ArrayList]$Arr, [String]$ObjName @@ -57,7 +59,7 @@ function Get-RalatedPolicies { return $result } -function Test-MtCaGaps { +function Test-MtCaGap { [CmdletBinding()] [OutputType([bool])] param () @@ -138,14 +140,14 @@ function Test-MtCaGaps { 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." + [System.Collections.ArrayList]$differencesUsers = @(Get-ObjectDifference -excludedObjects $excludedUsers -includedObjects $includedUsers) + [System.Collections.ArrayList]$differencesGroups = @(Get-ObjectDifference -excludedObjects $excludedGroups -includedObjects $includedGroups) + [System.Collections.ArrayList]$differencesRoles = @(Get-ObjectDifference -excludedObjects $excludedRoles -includedObjects $includedRoles) + [System.Collections.ArrayList]$differencesApplications = @(Get-ObjectDifference -excludedObjects $excludedApplications -includedObjects $includedApplications) + [System.Collections.ArrayList]$differencesServicePrincipals = @(Get-ObjectDifference -excludedObjects $excludedServicePrincipals -includedObjects $includedServicePrincipals) + [System.Collections.ArrayList]$differencesLocations = @(Get-ObjectDifference -excludedObjects $excludedLocations -includedObjects $includedLocations) + [System.Collections.ArrayList]$differencesPlatforms = @(Get-ObjectDifference -excludedObjects $excludedPlatforms -includedObjects $includedPlatforms) + Write-Verbose "Finished searching for gaps in policies." # Check if all excluded objects have fallbacks if ( @@ -167,7 +169,7 @@ function Test-MtCaGaps { $testResult = "The following user objects did not have a fallback:`n`n" $differencesUsers | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } # Add group objects to results @@ -175,7 +177,7 @@ function Test-MtCaGaps { $testResult += "The following group objects did not have a fallback:`n`n" $differencesGroups | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } # Add role objects to results @@ -183,7 +185,7 @@ function Test-MtCaGaps { $testResult += "The following role objects did not have a fallback:`n`n" $differencesRoles | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } # Add application objects to results @@ -191,7 +193,7 @@ function Test-MtCaGaps { $testResult += "The following application objects did not have a fallback:`n`n" $differencesApplications | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } # Add service principal objects to results @@ -199,7 +201,7 @@ function Test-MtCaGaps { $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 $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } # Add location objects to results @@ -207,7 +209,7 @@ function Test-MtCaGaps { $testResult += "The following location objects did not have a fallback:`n`n" $differencesLocations | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } # Add platform objects to results @@ -215,7 +217,7 @@ function Test-MtCaGaps { $testResult += "The following platform objects did not have a fallback:`n`n" $differencesPlatforms | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RalatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ } } } diff --git a/tests/Maester/Entra/Test-ConditionalAccessBaseline.Tests.ps1 b/tests/Maester/Entra/Test-ConditionalAccessBaseline.Tests.ps1 index b2f98816..028d7b63 100644 --- a/tests/Maester/Entra/Test-ConditionalAccessBaseline.Tests.ps1 +++ b/tests/Maester/Entra/Test-ConditionalAccessBaseline.Tests.ps1 @@ -60,7 +60,7 @@ Test-MtCaGroupsRestricted | Should -Be $true -Because "there is one or more policy 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-MtCaGaps | 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 is one ore more object excluded without an include fallback in another policy." } 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" { From bb7d212cec94c7fe0a954b0e8ab0fe2037e819ed Mon Sep 17 00:00:00 2001 From: Snozz Date: Mon, 26 Aug 2024 15:55:38 -0700 Subject: [PATCH 11/12] one more --- powershell/public/Test-MtCaGap.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/powershell/public/Test-MtCaGap.ps1 b/powershell/public/Test-MtCaGap.ps1 index e0d95a9e..1dd9a296 100644 --- a/powershell/public/Test-MtCaGap.ps1 +++ b/powershell/public/Test-MtCaGap.ps1 @@ -39,7 +39,7 @@ function Get-ObjectDifference { return $objectDifferences } -function Get-RelatedPolicies { +function Get-RelatedPolicy { [CmdletBinding()] [OutputType([string])] param ( @@ -169,7 +169,7 @@ function Test-MtCaGap { $testResult = "The following user objects did not have a fallback:`n`n" $differencesUsers | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } # Add group objects to results @@ -177,7 +177,7 @@ function Test-MtCaGap { $testResult += "The following group objects did not have a fallback:`n`n" $differencesGroups | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } # Add role objects to results @@ -185,7 +185,7 @@ function Test-MtCaGap { $testResult += "The following role objects did not have a fallback:`n`n" $differencesRoles | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } # Add application objects to results @@ -193,7 +193,7 @@ function Test-MtCaGap { $testResult += "The following application objects did not have a fallback:`n`n" $differencesApplications | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } # Add service principal objects to results @@ -201,7 +201,7 @@ function Test-MtCaGap { $testResult += "The following service principal objects did not have a fallback:`n`n" $differencesServicePrincipals | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } # Add location objects to results @@ -209,7 +209,7 @@ function Test-MtCaGap { $testResult += "The following location objects did not have a fallback:`n`n" $differencesLocations | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } # Add platform objects to results @@ -217,7 +217,7 @@ function Test-MtCaGap { $testResult += "The following platform objects did not have a fallback:`n`n" $differencesPlatforms | ForEach-Object { $testResult += " - $_`n`n" - $testResult += Get-RelatedPolicies -Arr $mappingArray -ObjName $_ + $testResult += Get-RelatedPolicy -Arr $mappingArray -ObjName $_ } } } From 278d8e04082477b7c409dc288db89251837fc4c5 Mon Sep 17 00:00:00 2001 From: Snozz Date: Mon, 26 Aug 2024 16:01:57 -0700 Subject: [PATCH 12/12] Function docs --- powershell/public/Test-MtCaGap.ps1 | 41 +++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/powershell/public/Test-MtCaGap.ps1 b/powershell/public/Test-MtCaGap.ps1 index 1dd9a296..247a627f 100644 --- a/powershell/public/Test-MtCaGap.ps1 +++ b/powershell/public/Test-MtCaGap.ps1 @@ -1,20 +1,15 @@ <# .Synopsis - This function checks if all objects found in policy exclusions are found in policy inclusions. + This function compares to object arrays .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 + Provides the differences in objects between two arrays of objects. .Example - Test-MtCaGap + Get-ObjectDifference .LINK - https://maester.dev/docs/commands/Test-MtCaGap + https://maester.dev/docs/commands/Get-ObjectDifference #> function Get-ObjectDifference { [CmdletBinding()] @@ -39,6 +34,19 @@ function Get-ObjectDifference { return $objectDifferences } +<# +.Synopsis + Provides MarkDown text for specific array of objects + +.Description + Returns a structured MarkDown string resolving objects + +.Example + Get-RelatedPolicy + +.LINK + https://maester.dev/docs/commands/Get-RelatedPolicy +#> function Get-RelatedPolicy { [CmdletBinding()] [OutputType([string])] @@ -59,6 +67,21 @@ function Get-RelatedPolicy { return $result } +<# +.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. + +.Example + Test-MtCaGap + +.LINK + https://maester.dev/docs/commands/Test-MtCaGap +#> function Test-MtCaGap { [CmdletBinding()] [OutputType([bool])]