From de17a1e40258b8de8cf4b76298ee86ae47775934 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Thu, 6 Jun 2024 16:32:41 -0700 Subject: [PATCH 01/22] Add example config file for ignoring policies --- .../Sample-Config-Files/ignore_policies.yaml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml diff --git a/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml new file mode 100644 index 0000000000..9052f46fef --- /dev/null +++ b/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml @@ -0,0 +1,34 @@ +Description: | + SCuBAGear YAML Configuration file with custom variables + This configuration shows a standard SCuBAGear set of parameters to run + but also includes examples of configuring specific policies to be ignored + by ScubaGear baseline. Any ignored policies should be carefully considered + and documented as part of an organization's cybersecurity risk management + program process and practices. +ProductNames: + - exo +M365Environment: commercial +OPAPath: . +LogIn: true +DisconnectOnExit: false +OutPath: . +OutFolderName: M365BaselineConformance +OutProviderFileName: ProviderSettingsExport +OutRegoFileName: TestResults +OutReportName: BaselineReports +Exo: + MS.EXO.4.3v1: + Ignore: true + IgnoreRationale: + "This policy is not applicable to our company as it only applies + to federal, executive branch, departments and agencies." +Defender: + MS.DEFENDER.2.1v1: + Ignore: true + IgnoreRationale: "Phishing protections provided by a third-party service." + MS.DEFENDER.2.2v1: + Ignore: true + IgnoreRationale: "Phishing protections provided by a third-party service." + MS.DEFENDER.2.3v1: + Ignore: true + IgnoreRationale: "Phishing protections provided by a third-party service." \ No newline at end of file From 33246ab2f016919e6b6cc91bdf152795330b67ce Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 7 Jun 2024 10:39:21 -0700 Subject: [PATCH 02/22] Added support for skipping policies to the report generation script. --- .../Modules/CreateReport/CreateReport.psm1 | 43 ++++++++++++++++ .../Modules/CreateReport/scripts/main.js | 3 ++ .../Sample-Config-Files/ignore_policies.yaml | 51 +++++++++++++------ 3 files changed, 82 insertions(+), 15 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 16baac85d5..5d8f85e2a2 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -101,6 +101,27 @@ function New-Report { $Test = $TestResults | Where-Object -Property PolicyId -eq $Control.Id if ($null -ne $Test){ + $Skip = $false + if (Test-Contains $SettingsExport.scuba_config $BaselineName) { + if (Test-Contains $SettingsExport.scuba_config.$BaselineName "IgnorePolicy") { + if (Test-Contains $SettingsExport.scuba_config.$BaselineName.IgnorePolicy $Control.Id) { + $Skip = $true + } + } + } + if ($Skip) { + $ReportSummary.Passes += 1 + $SkipRationale = $SettingsExport.scuba_config.$BaselineName.IgnorePolicy.$($Control.Id) + $Fragment += [pscustomobject]@{ + "Control ID"=$Control.Id + "Requirement"=$Control.Value + "Result"= "Skipped" + "Criticality"= $Test.Criticality + "Details"= "Test skipped by user. Rationale: `"$($SkipRationale)`"" + } + continue + } + $MissingCommands = $Test.Commandlet | Where-Object {$SettingsExport."$($BaselineName)_successful_commands" -notcontains $_} if ($MissingCommands.Count -gt 0) { @@ -277,6 +298,28 @@ function New-Report { $ReportSummary } +function Test-Contains { + <# + .Description + Tests if a custom PowerShell object contains a given key. + .Functionality + Internal + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [PSCustomObject] + $Object, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $Key + ) + $Object.psobject.properties.name -Contains $Key +} + function Import-SecureBaseline{ <# .Description diff --git a/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js b/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js index b0171af273..95f4355c11 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js +++ b/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js @@ -25,6 +25,9 @@ const colorRows = () => { else if (rows[i].children[statusCol].innerHTML === "Pass") { rows[i].style.background = "var(--test-pass)"; } + else if (rows[i].children[statusCol].innerHTML === "Skipped") { + rows[i].style.background = "var(--test-pass)"; + } else if (rows[i].children[criticalityCol].innerHTML.includes("Not-Implemented")) { rows[i].style.background = "var(--test-other)"; } diff --git a/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml index 9052f46fef..099902bcd9 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml @@ -7,6 +7,8 @@ Description: | program process and practices. ProductNames: - exo + - teams + - defender M365Environment: commercial OPAPath: . LogIn: true @@ -17,18 +19,37 @@ OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports Exo: - MS.EXO.4.3v1: - Ignore: true - IgnoreRationale: - "This policy is not applicable to our company as it only applies - to federal, executive branch, departments and agencies." -Defender: - MS.DEFENDER.2.1v1: - Ignore: true - IgnoreRationale: "Phishing protections provided by a third-party service." - MS.DEFENDER.2.2v1: - Ignore: true - IgnoreRationale: "Phishing protections provided by a third-party service." - MS.DEFENDER.2.3v1: - Ignore: true - IgnoreRationale: "Phishing protections provided by a third-party service." \ No newline at end of file + IgnorePolicy: + MS.EXO.4.3v1: "This policy is not applicable to our company as it only applies + to federal, executive branch, departments and agencies." # Assuming this config file corresponds to a private + # company, not a gov entity + MS.EXO.8.1v1: &DefenderRationale "Service provided via Microsoft M365 Defender." + MS.EXO.8.2v1: *DefenderRationale + MS.EXO.9.1v1: *DefenderRationale + MS.EXO.9.2v1: *DefenderRationale + MS.EXO.9.3v1: *DefenderRationale + MS.EXO.10.1v1: *DefenderRationale + MS.EXO.10.2v1: *DefenderRationale + MS.EXO.10.3v1: *DefenderRationale + MS.EXO.11.1v1: *DefenderRationale + MS.EXO.11.2v1: *DefenderRationale + MS.EXO.11.3v1: *DefenderRationale + MS.EXO.14.1v1: *DefenderRationale + MS.EXO.14.2v1: *DefenderRationale + MS.EXO.14.3v1: *DefenderRationale + MS.EXO.15.1v1: *DefenderRationale + MS.EXO.15.2v1: *DefenderRationale + MS.EXO.15.3v1: *DefenderRationale + MS.EXO.16.1v1: *DefenderRationale + MS.EXO.16.2v1: *DefenderRationale + MS.EXO.17.1v1: *DefenderRationale + MS.EXO.17.2v1: *DefenderRationale + MS.EXO.17.3v1: *DefenderRationale +Teams: + IgnorePolicy: + MS.TEAMS.6.1v1: *DefenderRationale + MS.TEAMS.6.2v1: *DefenderRationale + MS.TEAMS.7.1v1: *DefenderRationale + MS.TEAMS.7.2v1: *DefenderRationale + MS.TEAMS.8.1v1: *DefenderRationale + MS.TEAMS.8.2v1: *DefenderRationale From c7eacc4dd5d6d344794d0bcb7050ed3eba5e8675 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Mon, 10 Jun 2024 11:41:07 -0700 Subject: [PATCH 03/22] Modify report summary to show number of skips --- .../ScubaGear/Modules/CreateReport/CreateReport.psm1 | 3 ++- PowerShell/ScubaGear/Modules/Orchestrator.psm1 | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 5d8f85e2a2..39bb01f4c9 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -88,6 +88,7 @@ function New-Report { "Warnings" = 0; "Failures" = 0; "Passes" = 0; + "Skips" = 0; "Manual" = 0; "Errors" = 0; "Date" = $SettingsExport.date; @@ -110,7 +111,7 @@ function New-Report { } } if ($Skip) { - $ReportSummary.Passes += 1 + $ReportSummary.Skips += 1 $SkipRationale = $SettingsExport.scuba_config.$BaselineName.IgnorePolicy.$($Control.Id) $Fragment += [pscustomobject]@{ "Control ID"=$Control.Id diff --git a/PowerShell/ScubaGear/Modules/Orchestrator.psm1 b/PowerShell/ScubaGear/Modules/Orchestrator.psm1 index 8ef74da93d..306ed03b5b 100644 --- a/PowerShell/ScubaGear/Modules/Orchestrator.psm1 +++ b/PowerShell/ScubaGear/Modules/Orchestrator.psm1 @@ -1005,8 +1005,12 @@ function Invoke-ReportCreation { $LinkPath = "$($IndividualReportFolderName)/$($BaselineName)Report.html" $LinkClassName = '"individual_reports"' # uses no escape characters $Link = "$($FullName)" - - $PassesSummary = "
$($Report.Passes) tests passed
" + if ($Report.Skips -gt 0) { + $PassesSummary = "
$($Report.Passes) passed / $($Report.Passes) skipped
" + } + else { + $PassesSummary = "
$($Report.Passes) tests passed
" + } $WarningsSummary = "
" $FailuresSummary = "
" $BaselineURL = "

Baseline Documents

" From 55192fe042ae268fae37d5fa7b62b487f162fa7e Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Mon, 10 Jun 2024 11:44:17 -0700 Subject: [PATCH 04/22] Correct bug with skip summary --- PowerShell/ScubaGear/Modules/Orchestrator.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerShell/ScubaGear/Modules/Orchestrator.psm1 b/PowerShell/ScubaGear/Modules/Orchestrator.psm1 index 306ed03b5b..7bcf329dec 100644 --- a/PowerShell/ScubaGear/Modules/Orchestrator.psm1 +++ b/PowerShell/ScubaGear/Modules/Orchestrator.psm1 @@ -1006,7 +1006,7 @@ function Invoke-ReportCreation { $LinkClassName = '"individual_reports"' # uses no escape characters $Link = "$($FullName)" if ($Report.Skips -gt 0) { - $PassesSummary = "
$($Report.Passes) passed / $($Report.Passes) skipped
" + $PassesSummary = "
$($Report.Passes) passed / $($Report.Skips) skipped
" } else { $PassesSummary = "
$($Report.Passes) tests passed
" From e79bc3d2c4e97ffb4795a67ead26883435e51636 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Mon, 10 Jun 2024 12:41:34 -0700 Subject: [PATCH 05/22] Remove trailing whitespace --- .../ScubaGear/Modules/CreateReport/CreateReport.psm1 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 39bb01f4c9..54dd4dbd07 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -113,12 +113,19 @@ function New-Report { if ($Skip) { $ReportSummary.Skips += 1 $SkipRationale = $SettingsExport.scuba_config.$BaselineName.IgnorePolicy.$($Control.Id) + if ([string]::IsNullOrEmpty($SkipRationale)) { + Write-Warning "Config file indicates skipping $($Control.Id), but no rationale provided." + $SkipRationale = "Rationale not provided." + } + else { + $SkipRationale = "`"$($SkipRationale)`"" + } $Fragment += [pscustomobject]@{ "Control ID"=$Control.Id "Requirement"=$Control.Value "Result"= "Skipped" "Criticality"= $Test.Criticality - "Details"= "Test skipped by user. Rationale: `"$($SkipRationale)`"" + "Details"= "Test skipped by user. $($SkipRationale)" } continue } @@ -312,7 +319,7 @@ function Test-Contains { [ValidateNotNullOrEmpty()] [PSCustomObject] $Object, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] From 50ddbdc3d906273642d0c227c2ae17e723c9fa38 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Wed, 19 Jun 2024 20:26:24 -0700 Subject: [PATCH 06/22] Rename skipped to omitted; color omitted gray; add new summary box for omitted --- .../Modules/CreateReport/CreateReport.psm1 | 28 +++++++++---------- .../Modules/CreateReport/scripts/main.js | 4 +-- .../CreateReport/styles/ParentReportStyle.css | 2 +- .../ScubaGear/Modules/Orchestrator.psm1 | 27 ++++++++++-------- ...gnore_policies.yaml => omit_policies.yaml} | 4 +-- 5 files changed, 35 insertions(+), 30 deletions(-) rename PowerShell/ScubaGear/Sample-Config-Files/{ignore_policies.yaml => omit_policies.yaml} (95%) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 54dd4dbd07..b87f5a3da7 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -88,7 +88,7 @@ function New-Report { "Warnings" = 0; "Failures" = 0; "Passes" = 0; - "Skips" = 0; + "Omits" = 0; "Manual" = 0; "Errors" = 0; "Date" = $SettingsExport.date; @@ -102,30 +102,30 @@ function New-Report { $Test = $TestResults | Where-Object -Property PolicyId -eq $Control.Id if ($null -ne $Test){ - $Skip = $false + $Omit = $false if (Test-Contains $SettingsExport.scuba_config $BaselineName) { - if (Test-Contains $SettingsExport.scuba_config.$BaselineName "IgnorePolicy") { - if (Test-Contains $SettingsExport.scuba_config.$BaselineName.IgnorePolicy $Control.Id) { - $Skip = $true + if (Test-Contains $SettingsExport.scuba_config.$BaselineName "OmitPolicy") { + if (Test-Contains $SettingsExport.scuba_config.$BaselineName.OmitPolicy $Control.Id) { + $Omit = $true } } } - if ($Skip) { - $ReportSummary.Skips += 1 - $SkipRationale = $SettingsExport.scuba_config.$BaselineName.IgnorePolicy.$($Control.Id) - if ([string]::IsNullOrEmpty($SkipRationale)) { - Write-Warning "Config file indicates skipping $($Control.Id), but no rationale provided." - $SkipRationale = "Rationale not provided." + if ($Omit) { + $ReportSummary.Omits += 1 + $OmitRationale = $SettingsExport.scuba_config.$BaselineName.OmitPolicy.$($Control.Id) + if ([string]::IsNullOrEmpty($OmitRationale)) { + Write-Warning "Config file indicates omitting $($Control.Id), but no rationale provided." + $OmitRationale = "Rationale not provided." } else { - $SkipRationale = "`"$($SkipRationale)`"" + $OmitRationale = "`"$($OmitRationale)`"" } $Fragment += [pscustomobject]@{ "Control ID"=$Control.Id "Requirement"=$Control.Value - "Result"= "Skipped" + "Result"= "Omitted" "Criticality"= $Test.Criticality - "Details"= "Test skipped by user. $($SkipRationale)" + "Details"= "Test skipped by user. $($OmitRationale)" } continue } diff --git a/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js b/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js index 95f4355c11..e3131e24dd 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js +++ b/PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js @@ -25,8 +25,8 @@ const colorRows = () => { else if (rows[i].children[statusCol].innerHTML === "Pass") { rows[i].style.background = "var(--test-pass)"; } - else if (rows[i].children[statusCol].innerHTML === "Skipped") { - rows[i].style.background = "var(--test-pass)"; + else if (rows[i].children[statusCol].innerHTML === "Omitted") { + rows[i].style.background = "var(--test-other)"; } else if (rows[i].children[criticalityCol].innerHTML.includes("Not-Implemented")) { rows[i].style.background = "var(--test-other)"; diff --git a/PowerShell/ScubaGear/Modules/CreateReport/styles/ParentReportStyle.css b/PowerShell/ScubaGear/Modules/CreateReport/styles/ParentReportStyle.css index b8f03feaab..bfbcc56cd1 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/styles/ParentReportStyle.css +++ b/PowerShell/ScubaGear/Modules/CreateReport/styles/ParentReportStyle.css @@ -15,7 +15,7 @@ footer { display: inline-block; padding: 0.313em; border-radius: 0.313em; - min-width: 140px; + min-width: 100px; text-align: center; } diff --git a/PowerShell/ScubaGear/Modules/Orchestrator.psm1 b/PowerShell/ScubaGear/Modules/Orchestrator.psm1 index 7bcf329dec..9aef9a352b 100644 --- a/PowerShell/ScubaGear/Modules/Orchestrator.psm1 +++ b/PowerShell/ScubaGear/Modules/Orchestrator.psm1 @@ -1005,17 +1005,18 @@ function Invoke-ReportCreation { $LinkPath = "$($IndividualReportFolderName)/$($BaselineName)Report.html" $LinkClassName = '"individual_reports"' # uses no escape characters $Link = "$($FullName)" - if ($Report.Skips -gt 0) { - $PassesSummary = "
$($Report.Passes) passed / $($Report.Skips) skipped
" - } - else { - $PassesSummary = "
$($Report.Passes) tests passed
" - } + $PassesSummary = "
" $WarningsSummary = "
" $FailuresSummary = "
" $BaselineURL = "

Baseline Documents

" $ManualSummary = "
" - $ErrorSummary = "
" + $OmitSummary = "
" + $ErrorSummary = "" + + if ($Report.Passes -gt 0) { + $Noun = Pluralize -SingularNoun "pass" -PluralNoun "passes" -Count $Report.Passes + $PassesSummary = "
$($Report.Passes) $($Noun)
" + } if ($Report.Warnings -gt 0) { $Noun = Pluralize -SingularNoun "warning" -PluralNoun "warnings" -Count $Report.Warnings @@ -1023,13 +1024,17 @@ function Invoke-ReportCreation { } if ($Report.Failures -gt 0) { - $Noun = Pluralize -SingularNoun "test" -PluralNoun "tests" -Count $Report.Failures - $FailuresSummary = "
$($Report.Failures) $($Noun) failed
" + $Noun = Pluralize -SingularNoun "failure" -PluralNoun "failures" -Count $Report.Failures + $FailuresSummary = "
$($Report.Failures) $($Noun)
" } if ($Report.Manual -gt 0) { $Noun = Pluralize -SingularNoun "check" -PluralNoun "checks" -Count $Report.Manual - $ManualSummary = "
$($Report.Manual) manual $($Noun) needed
" + $ManualSummary = "
$($Report.Manual) manual $($Noun)
" + } + + if ($Report.Omits -gt 0) { + $OmitSummary = "
$($Report.Omits) omitted
" } if ($Report.Errors -gt 0) { @@ -1039,7 +1044,7 @@ function Invoke-ReportCreation { $Fragment += [pscustomobject]@{ "Baseline Conformance Reports" = $Link; - "Details" = "$($PassesSummary) $($WarningsSummary) $($FailuresSummary) $($ManualSummary) $($ErrorSummary)" + "Details" = "$($PassesSummary) $($WarningsSummary) $($FailuresSummary) $($ManualSummary) $($OmitSummary) $($ErrorSummary)" } } $TenantMetaData += [pscustomobject]@{ diff --git a/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml similarity index 95% rename from PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml index 099902bcd9..e6db7ca7fc 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/ignore_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml @@ -19,7 +19,7 @@ OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports Exo: - IgnorePolicy: + OmitPolicy: MS.EXO.4.3v1: "This policy is not applicable to our company as it only applies to federal, executive branch, departments and agencies." # Assuming this config file corresponds to a private # company, not a gov entity @@ -46,7 +46,7 @@ Exo: MS.EXO.17.2v1: *DefenderRationale MS.EXO.17.3v1: *DefenderRationale Teams: - IgnorePolicy: + OmitPolicy: MS.TEAMS.6.1v1: *DefenderRationale MS.TEAMS.6.2v1: *DefenderRationale MS.TEAMS.7.1v1: *DefenderRationale From 6716977a4c6d2feb71d3940d8f9a02e3ace64a13 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 21 Jun 2024 12:36:30 -0700 Subject: [PATCH 07/22] Change 'skipped' to 'disabled' --- PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index b87f5a3da7..63816a0642 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -125,7 +125,7 @@ function New-Report { "Requirement"=$Control.Value "Result"= "Omitted" "Criticality"= $Test.Criticality - "Details"= "Test skipped by user. $($OmitRationale)" + "Details"= "Test disabled by user. $($OmitRationale)" } continue } From b27be8b178ffd1c87465a0a9fbe12ca41da2d931 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Thu, 4 Jul 2024 11:00:41 -0700 Subject: [PATCH 08/22] Implement omission exiration date --- .../Modules/CreateReport/CreateReport.psm1 | 74 +++++++++++-- .../Sample-Config-Files/omit_policies.yaml | 100 ++++++++++++------ 2 files changed, 132 insertions(+), 42 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 63816a0642..09aff0c180 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -102,17 +102,12 @@ function New-Report { $Test = $TestResults | Where-Object -Property PolicyId -eq $Control.Id if ($null -ne $Test){ - $Omit = $false - if (Test-Contains $SettingsExport.scuba_config $BaselineName) { - if (Test-Contains $SettingsExport.scuba_config.$BaselineName "OmitPolicy") { - if (Test-Contains $SettingsExport.scuba_config.$BaselineName.OmitPolicy $Control.Id) { - $Omit = $true - } - } - } + # Check if the config file indicates the control should be omitted + $Config = $SettingsExport.scuba_config + $Omit = Get-OmissionState $Config $Control.Id if ($Omit) { $ReportSummary.Omits += 1 - $OmitRationale = $SettingsExport.scuba_config.$BaselineName.OmitPolicy.$($Control.Id) + $OmitRationale = $Config.$BaselineName.OmitPolicy.$($Control.Id).Rationale if ([string]::IsNullOrEmpty($OmitRationale)) { Write-Warning "Config file indicates omitting $($Control.Id), but no rationale provided." $OmitRationale = "Rationale not provided." @@ -328,6 +323,67 @@ function Test-Contains { $Object.psobject.properties.name -Contains $Key } +function Get-OmissionState { + <# + .Description + Determine if the supplied control was marked for omission in the config file. + .Functionality + Internal + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [PSCustomObject] + $Config, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $ControlId + ) + $Omit = $false + if (Test-Contains $Config $BaselineName) { + if (Test-Contains $Config.$BaselineName "OmitPolicy") { + if (Test-Contains $Config.$BaselineName.OmitPolicy $ControlId) { + # The config indicates the control should be omitted + if (Test-Contains $Config.$BaselineName.OmitPolicy.$($ControlId) "Expiration") { + # An expiration date for the omission expiration was provided. Evaluate the date + # to see if the control should still be omitted. + $Now = Get-Date + $ExpirationString = $Config.$BaselineName.OmitPolicy.$($ControlId).Expiration + try { + $ExpirationDate = Get-Date -Date $ExpirationString + if ($ExpirationDate -lt $Now) { + # The expiration date is passed, don't omit the policy + $Warning = "Config file indicates omitting $($ControlId), but the provided " + $Warning += "expiration date, $ExpirationString, has passed. Control will " + $Warning += "not be omitted." + Write-Warning $Warning + } + else { + # The expiration date is in the future, omit the policy + $Omit = $true + } + } + catch { + # Malformed date, don't omit the policy + $Warning = "Config file indicates omitting $($ControlId), but the provided " + $Warning += "expiration date, $ExpirationString, is malformed. The expected " + $Warning += "format is yyyy-mm-dd. Control will not be omitted." + Write-Warning $Warning + } + } + else { + # The expiration date was not provided, omit the policy + $Omit = $true + } + } + } + } + $Omit +} + function Import-SecureBaseline{ <# .Description diff --git a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml index e6db7ca7fc..207338e24d 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml @@ -1,8 +1,8 @@ Description: | SCuBAGear YAML Configuration file with custom variables This configuration shows a standard SCuBAGear set of parameters to run - but also includes examples of configuring specific policies to be ignored - by ScubaGear baseline. Any ignored policies should be carefully considered + but also includes examples of configuring specific policies to be omitted + from the ScubaGear output. Any omitted policies should be carefully considered and documented as part of an organization's cybersecurity risk management program process and practices. ProductNames: @@ -18,38 +18,72 @@ OutFolderName: M365BaselineConformance OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports +Organization: "cisaent.onmicrosoft.com" Exo: OmitPolicy: - MS.EXO.4.3v1: "This policy is not applicable to our company as it only applies - to federal, executive branch, departments and agencies." # Assuming this config file corresponds to a private - # company, not a gov entity - MS.EXO.8.1v1: &DefenderRationale "Service provided via Microsoft M365 Defender." - MS.EXO.8.2v1: *DefenderRationale - MS.EXO.9.1v1: *DefenderRationale - MS.EXO.9.2v1: *DefenderRationale - MS.EXO.9.3v1: *DefenderRationale - MS.EXO.10.1v1: *DefenderRationale - MS.EXO.10.2v1: *DefenderRationale - MS.EXO.10.3v1: *DefenderRationale - MS.EXO.11.1v1: *DefenderRationale - MS.EXO.11.2v1: *DefenderRationale - MS.EXO.11.3v1: *DefenderRationale - MS.EXO.14.1v1: *DefenderRationale - MS.EXO.14.2v1: *DefenderRationale - MS.EXO.14.3v1: *DefenderRationale - MS.EXO.15.1v1: *DefenderRationale - MS.EXO.15.2v1: *DefenderRationale - MS.EXO.15.3v1: *DefenderRationale - MS.EXO.16.1v1: *DefenderRationale - MS.EXO.16.2v1: *DefenderRationale - MS.EXO.17.1v1: *DefenderRationale - MS.EXO.17.2v1: *DefenderRationale - MS.EXO.17.3v1: *DefenderRationale + MS.EXO.2.2v1: + Rationale: "Known false positive; our SPF policy currently cannot to be retrieved via ScubaGear due to a split + horizon setup but is available publicly." + Expiration: "2023-1231" + MS.EXO.4.3v1: + Rationale: "This policy is not applicable to our company as it only applies + to federal, executive branch, departments and agencies." + # Assuming this config file corresponds to a private company, not a gov entity + MS.EXO.8.1v1: + Rationale: &DefenderRationale "Service provided via Microsoft M365 Defender." + MS.EXO.8.2v1: + Rationale: *DefenderRationale + MS.EXO.9.1v1: + Rationale: *DefenderRationale + MS.EXO.9.2v1: + Rationale: *DefenderRationale + MS.EXO.9.3v1: + Rationale: *DefenderRationale + MS.EXO.10.1v1: + Rationale: *DefenderRationale + MS.EXO.10.2v1: + Rationale: *DefenderRationale + MS.EXO.10.3v1: + Rationale: *DefenderRationale + MS.EXO.11.1v1: + Rationale: *DefenderRationale + MS.EXO.11.2v1: + Rationale: *DefenderRationale + MS.EXO.11.3v1: + Rationale: *DefenderRationale + MS.EXO.14.1v1: + Rationale: *DefenderRationale + MS.EXO.14.2v1: + Rationale: *DefenderRationale + MS.EXO.14.3v1: + Rationale: *DefenderRationale + MS.EXO.15.1v1: + Rationale: *DefenderRationale + MS.EXO.15.2v1: + Rationale: *DefenderRationale + MS.EXO.15.3v1: + Rationale: *DefenderRationale + MS.EXO.16.1v1: + Rationale: *DefenderRationale + MS.EXO.16.2v1: + Rationale: *DefenderRationale + MS.EXO.17.1v1: + Rationale: *DefenderRationale + MS.EXO.17.2v1: + Rationale: *DefenderRationale + MS.EXO.17.3v1: + Rationale: *DefenderRationale Teams: OmitPolicy: - MS.TEAMS.6.1v1: *DefenderRationale - MS.TEAMS.6.2v1: *DefenderRationale - MS.TEAMS.7.1v1: *DefenderRationale - MS.TEAMS.7.2v1: *DefenderRationale - MS.TEAMS.8.1v1: *DefenderRationale - MS.TEAMS.8.2v1: *DefenderRationale + MS.TEAMS.6.1v1: + Rationale: *DefenderRationale + MS.TEAMS.6.2v1: + Rationale: *DefenderRationale + MS.TEAMS.7.1v1: + Rationale: *DefenderRationale + MS.TEAMS.7.2v1: + Rationale: *DefenderRationale + MS.TEAMS.8.1v1: + Rationale: *DefenderRationale + MS.TEAMS.8.2v1: + Rationale: *DefenderRationale From 844d20118c4d341e678724cdb61ce98453bb669c Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Thu, 4 Jul 2024 11:01:25 -0700 Subject: [PATCH 09/22] Tweak config file --- PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml index 207338e24d..9b4c6f02a5 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml @@ -18,7 +18,6 @@ OutFolderName: M365BaselineConformance OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports -Organization: "cisaent.onmicrosoft.com" Exo: OmitPolicy: MS.EXO.2.2v1: From adb2939ec04bc0b2dec7f1818e2c250dabc67067 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 12:09:23 -0700 Subject: [PATCH 10/22] Added support for policy omission to New-Config function --- .../Modules/CreateReport/CreateReport.psm1 | 45 +++++++----- .../ScubaGear/Modules/Support/Support.psm1 | 70 ++++++++++++++++++- 2 files changed, 95 insertions(+), 20 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 09aff0c180..b9391dc84d 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -350,28 +350,35 @@ function Get-OmissionState { if (Test-Contains $Config.$BaselineName.OmitPolicy.$($ControlId) "Expiration") { # An expiration date for the omission expiration was provided. Evaluate the date # to see if the control should still be omitted. - $Now = Get-Date - $ExpirationString = $Config.$BaselineName.OmitPolicy.$($ControlId).Expiration - try { - $ExpirationDate = Get-Date -Date $ExpirationString - if ($ExpirationDate -lt $Now) { - # The expiration date is passed, don't omit the policy + if ($Config.$BaselineName.OmitPolicy.$($ControlId).Expiration -eq "") { + # If the Expiration date is an empty string, omit the policy + $Omit = $true + } + else { + # An expiration date was provided and it's not an empty string + $Now = Get-Date + $ExpirationString = $Config.$BaselineName.OmitPolicy.$($ControlId).Expiration + try { + $ExpirationDate = Get-Date -Date $ExpirationString + if ($ExpirationDate -lt $Now) { + # The expiration date is passed, don't omit the policy + $Warning = "Config file indicates omitting $($ControlId), but the provided " + $Warning += "expiration date, $ExpirationString, has passed. Control will " + $Warning += "not be omitted." + Write-Warning $Warning + } + else { + # The expiration date is in the future, omit the policy + $Omit = $true + } + } + catch { + # Malformed date, don't omit the policy $Warning = "Config file indicates omitting $($ControlId), but the provided " - $Warning += "expiration date, $ExpirationString, has passed. Control will " - $Warning += "not be omitted." + $Warning += "expiration date, $ExpirationString, is malformed. The expected " + $Warning += "format is yyyy-mm-dd. Control will not be omitted." Write-Warning $Warning } - else { - # The expiration date is in the future, omit the policy - $Omit = $true - } - } - catch { - # Malformed date, don't omit the policy - $Warning = "Config file indicates omitting $($ControlId), but the provided " - $Warning += "expiration date, $ExpirationString, is malformed. The expected " - $Warning += "format is yyyy-mm-dd. Control will not be omitted." - Write-Warning $Warning } } else { diff --git a/PowerShell/ScubaGear/Modules/Support/Support.psm1 b/PowerShell/ScubaGear/Modules/Support/Support.psm1 index 0a3b21b6c1..12deea7cc2 100644 --- a/PowerShell/ScubaGear/Modules/Support/Support.psm1 +++ b/PowerShell/ScubaGear/Modules/Support/Support.psm1 @@ -728,6 +728,9 @@ function New-Config { parameters. Additional parameters and variables not available on the command line can also be included in the file that will be provided to the tool for use in specific tests. + .Parameter OmitPolicy + A comma-separated list of policies to exclude from the ScubaGear report, e.g., MS.DEFENDER.1.1v1. + Note that the rationales will need to be manually added to the resulting config file. .Functionality Public #> @@ -812,7 +815,12 @@ function New-Config { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] - $ConfigLocation = "./" + $ConfigLocation = "./", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string[]] + $OmitPolicy = @() ) $Config = New-Object ([System.Collections.specialized.OrderedDictionary]) @@ -825,6 +833,12 @@ function New-Config { } } + if ($config.Contains("OmitPolicy")) { + # We don't want this parameter to be saved as a top-level key in the config, as this + # isn't the place ScubaGear will look for it. + $config.Remove("OmitPolicy") + } + $CapExclusionNamespace = @( "MS.AAD.1.1v1", "MS.AAD.2.1v1", @@ -849,6 +863,49 @@ function New-Config { $PartnerDomainImpersonationProtectionNamespace = "MS.DEFENDER.2.3v1" + $OmissionNamespace = "OmitPolicy" + + # List to track which policies the user specified in $OmitPolicies are properly formatted + $OmitPolicyValidated = @() + + # Hashmap to structure the ignored policies template + $OmitTemplate = @{} + + foreach ($Policy in $OmitPolicy) { + if (-not ($Policy -match "^ms\.[a-z]+\.[0-9]+\.[0-9]+v[0-9]+$")) { + # Note that -match is a case insensitive match + # Note that the regex does not validate the product name, this will be done + # later + $Warning = "The policy, $Policy, in the OmitPolicy parameter, is not a valid " + $Warning += "policy ID. Expected format 'MS.[PRODUCT].[GROUP].[NUMBER]v[VERSION]', " + $Warning += "e.g., 'MS.DEFENDER.1.1v1'. Skipping." + Write-Warning $Warning + Continue + } + $Product = ($Policy -Split "\.")[1] + # Here's where the product name is validated + if (-not ($ProductNames -Contains $Product)) { + $Warning = "The policy, $Policy, in the OmitPolicy parameter, is not encompassed by " + $Warning += "the products specified in the ProductName parameter. Skipping." + Write-Warning $Warning + Continue + } + if (-not $OmitTemplate.ContainsKey($Product)) { + $OmitTemplate[$Product] = @{ $OmissionNamespace = @{} } + } + # Ensure the policy ID is properly capitalized (i.e., all caps except for the "v1" portion) + $PolicyCapitalized = $Policy.Substring(0, $Policy.Length-2).ToUpper() + $Policy.SubString($Policy.Length-2) + $OmitPolicyValidated += $PolicyCapitalized + $OmitTemplate[$Product][$OmissionNamespace][$PolicyCapitalized] = @{ + "Rationale" = ""; + "Expiration" = ""; + } + } + + $Warning = "The following policies have been configured for omission: $($OmitPolicyValidated -Join ', '). " + $Warning += "Note that as the New-Config function does not support providing the rationale for omission via " + $Warning += "the commandline, you will need to open the resulting config file and manually enter the rationales." + Write-Warning $Warning $AadTemplate = New-Object ([System.Collections.specialized.OrderedDictionary]) $AadCapExclusions = New-Object ([System.Collections.specialized.OrderedDictionary]) @@ -903,6 +960,17 @@ function New-Config { } } } + foreach ($Product in $Products) { + if ($OmitTemplate.ContainsKey($Product)) { + $ProductTitleCase = $Product.Substring(0, 1).ToUpper()+$Product.ToLower().Substring(1).ToLower() + if ($config.Contains($ProductTitleCase)) { + $config[$ProductTitleCase][$OmissionNamespace] = $OmitTemplate[$Product][$OmissionNamespace] + } + else { + $config.add($ProductTitleCase, $OmitTemplate[$Product]) + } + } + } convertto-yaml $Config | set-content "$($ConfigLocation)/SampleConfig.yaml" } From a05709681be9fcc081152e129e8d1676d42a544a Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 12:43:51 -0700 Subject: [PATCH 11/22] Make it so that the OmitPolicy config key isn't nested under the product names --- .../Modules/CreateReport/CreateReport.psm1 | 74 ++++++++-------- .../ScubaGear/Modules/Support/Support.psm1 | 22 +---- .../Sample-Config-Files/omit_policies.yaml | 85 ++++--------------- 3 files changed, 57 insertions(+), 124 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index b9391dc84d..af18d98e98 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -107,7 +107,7 @@ function New-Report { $Omit = Get-OmissionState $Config $Control.Id if ($Omit) { $ReportSummary.Omits += 1 - $OmitRationale = $Config.$BaselineName.OmitPolicy.$($Control.Id).Rationale + $OmitRationale = $Config.OmitPolicy.$($Control.Id).Rationale if ([string]::IsNullOrEmpty($OmitRationale)) { Write-Warning "Config file indicates omitting $($Control.Id), but no rationale provided." $OmitRationale = "Rationale not provided." @@ -343,48 +343,46 @@ function Get-OmissionState { $ControlId ) $Omit = $false - if (Test-Contains $Config $BaselineName) { - if (Test-Contains $Config.$BaselineName "OmitPolicy") { - if (Test-Contains $Config.$BaselineName.OmitPolicy $ControlId) { - # The config indicates the control should be omitted - if (Test-Contains $Config.$BaselineName.OmitPolicy.$($ControlId) "Expiration") { - # An expiration date for the omission expiration was provided. Evaluate the date - # to see if the control should still be omitted. - if ($Config.$BaselineName.OmitPolicy.$($ControlId).Expiration -eq "") { - # If the Expiration date is an empty string, omit the policy - $Omit = $true - } - else { - # An expiration date was provided and it's not an empty string - $Now = Get-Date - $ExpirationString = $Config.$BaselineName.OmitPolicy.$($ControlId).Expiration - try { - $ExpirationDate = Get-Date -Date $ExpirationString - if ($ExpirationDate -lt $Now) { - # The expiration date is passed, don't omit the policy - $Warning = "Config file indicates omitting $($ControlId), but the provided " - $Warning += "expiration date, $ExpirationString, has passed. Control will " - $Warning += "not be omitted." - Write-Warning $Warning - } - else { - # The expiration date is in the future, omit the policy - $Omit = $true - } - } - catch { - # Malformed date, don't omit the policy + if (Test-Contains $Config "OmitPolicy") { + if (Test-Contains $Config.OmitPolicy $ControlId) { + # The config indicates the control should be omitted + if (Test-Contains $Config.OmitPolicy.$($ControlId) "Expiration") { + # An expiration date for the omission expiration was provided. Evaluate the date + # to see if the control should still be omitted. + if ($Config.OmitPolicy.$($ControlId).Expiration -eq "") { + # If the Expiration date is an empty string, omit the policy + $Omit = $true + } + else { + # An expiration date was provided and it's not an empty string + $Now = Get-Date + $ExpirationString = $Config.OmitPolicy.$($ControlId).Expiration + try { + $ExpirationDate = Get-Date -Date $ExpirationString + if ($ExpirationDate -lt $Now) { + # The expiration date is passed, don't omit the policy $Warning = "Config file indicates omitting $($ControlId), but the provided " - $Warning += "expiration date, $ExpirationString, is malformed. The expected " - $Warning += "format is yyyy-mm-dd. Control will not be omitted." + $Warning += "expiration date, $ExpirationString, has passed. Control will " + $Warning += "not be omitted." Write-Warning $Warning } + else { + # The expiration date is in the future, omit the policy + $Omit = $true + } + } + catch { + # Malformed date, don't omit the policy + $Warning = "Config file indicates omitting $($ControlId), but the provided " + $Warning += "expiration date, $ExpirationString, is malformed. The expected " + $Warning += "format is yyyy-mm-dd. Control will not be omitted." + Write-Warning $Warning } } - else { - # The expiration date was not provided, omit the policy - $Omit = $true - } + } + else { + # The expiration date was not provided, omit the policy + $Omit = $true } } } diff --git a/PowerShell/ScubaGear/Modules/Support/Support.psm1 b/PowerShell/ScubaGear/Modules/Support/Support.psm1 index 12deea7cc2..a5b4fa7c25 100644 --- a/PowerShell/ScubaGear/Modules/Support/Support.psm1 +++ b/PowerShell/ScubaGear/Modules/Support/Support.psm1 @@ -834,8 +834,8 @@ function New-Config { } if ($config.Contains("OmitPolicy")) { - # We don't want this parameter to be saved as a top-level key in the config, as this - # isn't the place ScubaGear will look for it. + # We don't want to immediately save this parameter to the config, as it's not in the right + # format yet. $config.Remove("OmitPolicy") } @@ -869,7 +869,7 @@ function New-Config { $OmitPolicyValidated = @() # Hashmap to structure the ignored policies template - $OmitTemplate = @{} + $config[$OmissionNamespace] = @{} foreach ($Policy in $OmitPolicy) { if (-not ($Policy -match "^ms\.[a-z]+\.[0-9]+\.[0-9]+v[0-9]+$")) { @@ -890,13 +890,10 @@ function New-Config { Write-Warning $Warning Continue } - if (-not $OmitTemplate.ContainsKey($Product)) { - $OmitTemplate[$Product] = @{ $OmissionNamespace = @{} } - } # Ensure the policy ID is properly capitalized (i.e., all caps except for the "v1" portion) $PolicyCapitalized = $Policy.Substring(0, $Policy.Length-2).ToUpper() + $Policy.SubString($Policy.Length-2) $OmitPolicyValidated += $PolicyCapitalized - $OmitTemplate[$Product][$OmissionNamespace][$PolicyCapitalized] = @{ + $config[$OmissionNamespace][$PolicyCapitalized] = @{ "Rationale" = ""; "Expiration" = ""; } @@ -960,17 +957,6 @@ function New-Config { } } } - foreach ($Product in $Products) { - if ($OmitTemplate.ContainsKey($Product)) { - $ProductTitleCase = $Product.Substring(0, 1).ToUpper()+$Product.ToLower().Substring(1).ToLower() - if ($config.Contains($ProductTitleCase)) { - $config[$ProductTitleCase][$OmissionNamespace] = $OmitTemplate[$Product][$OmissionNamespace] - } - else { - $config.add($ProductTitleCase, $OmitTemplate[$Product]) - } - } - } convertto-yaml $Config | set-content "$($ConfigLocation)/SampleConfig.yaml" } diff --git a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml index 9b4c6f02a5..0b69160180 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml @@ -18,71 +18,20 @@ OutFolderName: M365BaselineConformance OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports -Exo: - OmitPolicy: - MS.EXO.2.2v1: - Rationale: "Known false positive; our SPF policy currently cannot to be retrieved via ScubaGear due to a split - horizon setup but is available publicly." - Expiration: "2023-1231" - MS.EXO.4.3v1: - Rationale: "This policy is not applicable to our company as it only applies - to federal, executive branch, departments and agencies." - # Assuming this config file corresponds to a private company, not a gov entity - MS.EXO.8.1v1: - Rationale: &DefenderRationale "Service provided via Microsoft M365 Defender." - MS.EXO.8.2v1: - Rationale: *DefenderRationale - MS.EXO.9.1v1: - Rationale: *DefenderRationale - MS.EXO.9.2v1: - Rationale: *DefenderRationale - MS.EXO.9.3v1: - Rationale: *DefenderRationale - MS.EXO.10.1v1: - Rationale: *DefenderRationale - MS.EXO.10.2v1: - Rationale: *DefenderRationale - MS.EXO.10.3v1: - Rationale: *DefenderRationale - MS.EXO.11.1v1: - Rationale: *DefenderRationale - MS.EXO.11.2v1: - Rationale: *DefenderRationale - MS.EXO.11.3v1: - Rationale: *DefenderRationale - MS.EXO.14.1v1: - Rationale: *DefenderRationale - MS.EXO.14.2v1: - Rationale: *DefenderRationale - MS.EXO.14.3v1: - Rationale: *DefenderRationale - MS.EXO.15.1v1: - Rationale: *DefenderRationale - MS.EXO.15.2v1: - Rationale: *DefenderRationale - MS.EXO.15.3v1: - Rationale: *DefenderRationale - MS.EXO.16.1v1: - Rationale: *DefenderRationale - MS.EXO.16.2v1: - Rationale: *DefenderRationale - MS.EXO.17.1v1: - Rationale: *DefenderRationale - MS.EXO.17.2v1: - Rationale: *DefenderRationale - MS.EXO.17.3v1: - Rationale: *DefenderRationale -Teams: - OmitPolicy: - MS.TEAMS.6.1v1: - Rationale: *DefenderRationale - MS.TEAMS.6.2v1: - Rationale: *DefenderRationale - MS.TEAMS.7.1v1: - Rationale: *DefenderRationale - MS.TEAMS.7.2v1: - Rationale: *DefenderRationale - MS.TEAMS.8.1v1: - Rationale: *DefenderRationale - MS.TEAMS.8.2v1: - Rationale: *DefenderRationale +OmitPolicy: + MS.EXO.2.2v1: + Rationale: "Known false positive; our SPF policy currently cannot to be retrieved via ScubaGear due to a split + horizon setup but is available publicly." + Expiration: "2023-12-31" + MS.TEAMS.6.1v1: + Rationale: &DefenderRationale "Service provided via Microsoft M365 Defender." + MS.TEAMS.6.2v1: + Rationale: *DefenderRationale + MS.TEAMS.7.1v1: + Rationale: *DefenderRationale + MS.TEAMS.7.2v1: + Rationale: *DefenderRationale + MS.TEAMS.8.1v1: + Rationale: *DefenderRationale + MS.TEAMS.8.2v1: + Rationale: *DefenderRationale From fa6a74a00f4e6064569aac77a1bae0b6624cead6 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 13:13:46 -0700 Subject: [PATCH 12/22] Add documentation for omitting policies --- docs/configuration/configuration.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5c3c1059af..c19b65faec 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -8,7 +8,7 @@ Most of the `Invoke-SCuBA` cmdlet [parameters](parameters.md) can be placed into ## Sample Configuration Files -[Sample config files](https://github.com/cisagov/ScubaGear/tree/main/PowerShell/ScubaGear/Sample-Config-Files) are available in the repo. Four of these sample config files are explained in more detail in the sections below. +[Sample config files](https://github.com/cisagov/ScubaGear/tree/main/PowerShell/ScubaGear/Sample-Config-Files) are available in the repo. Five of these sample config files are explained in more detail in the sections below. ### Basic Use @@ -90,6 +90,22 @@ ScubaGear's support module can generate an empty sample config file. Running the New-Config ``` +## Omit Policies + +In some cases, it may be appropriate to exclude policies from ScubaGear evaluation. For example: +- False positives due to a policy being implemented via a third-party service +- Policies not applicable to your organization (e.g., MS.EXO.4.3v1, which is only applicable to federal, executive branch, departments and agencies) + +**However, exclusions can introduce blind spots to your system and must be managed carefully.** + +The `OmitPolicy` top-level key in the [configuration](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the IDs of policies that should be excluded from the ScubaGear report. Excluded policies will show up as "Omitted" in the HTML report and will be colored gray. + +For each exclusion, the config allows you to indicate the following: +- `Rationale`: The reason the policy should be excluded from the report. This value will be displayed in the "Details" column of the report. ScubaGear will output a warning if no rationale is provided. +- `Expiration`: Optional. A date after which the policy should no longer be omitted from the report. The expected format is yyyy-mm-dd. + +Policy omissions can be provided to the `New-Config` function as a comma-separated list of policy IDs under the `OmitPolicy` parameter. However, the `New-Config` function does not allow you to specify the rationales or expiration dates via the commandline; these must be manually entered into the resulting config file. + ## Product-specific Configuration Config files can include a top-level level key for a given product whose values are related to that specific product. For example, look for the value of `Defender` in this [Defender config file](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/defender-config.yaml). Currently, only Entra ID and Defender use this extra configuration. From 791f830ee3c17d2aa66b2ff18a8a3e5c10c0dad0 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 13:20:04 -0700 Subject: [PATCH 13/22] Minor tweaks to the config md --- docs/configuration/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index c19b65faec..158cf834bd 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -8,7 +8,7 @@ Most of the `Invoke-SCuBA` cmdlet [parameters](parameters.md) can be placed into ## Sample Configuration Files -[Sample config files](https://github.com/cisagov/ScubaGear/tree/main/PowerShell/ScubaGear/Sample-Config-Files) are available in the repo. Five of these sample config files are explained in more detail in the sections below. +[Sample config files](https://github.com/cisagov/ScubaGear/tree/main/PowerShell/ScubaGear/Sample-Config-Files) are available in the repo. Four of these sample config files are explained in more detail in the sections below. ### Basic Use @@ -96,7 +96,7 @@ In some cases, it may be appropriate to exclude policies from ScubaGear evaluati - False positives due to a policy being implemented via a third-party service - Policies not applicable to your organization (e.g., MS.EXO.4.3v1, which is only applicable to federal, executive branch, departments and agencies) -**However, exclusions can introduce blind spots to your system and must be managed carefully.** +Exclusions must only be used if they are approved within an organization's security risk acceptance process. **Exclusions can introduce blind spots to your system and must be managed carefully.** The `OmitPolicy` top-level key in the [configuration](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the IDs of policies that should be excluded from the ScubaGear report. Excluded policies will show up as "Omitted" in the HTML report and will be colored gray. From 403e51a842367e5bd37f7b5ccce904862177d7a1 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 13:21:56 -0700 Subject: [PATCH 14/22] Merge two paragraphs --- docs/configuration/configuration.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 158cf834bd..1b58e650d6 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -96,9 +96,7 @@ In some cases, it may be appropriate to exclude policies from ScubaGear evaluati - False positives due to a policy being implemented via a third-party service - Policies not applicable to your organization (e.g., MS.EXO.4.3v1, which is only applicable to federal, executive branch, departments and agencies) -Exclusions must only be used if they are approved within an organization's security risk acceptance process. **Exclusions can introduce blind spots to your system and must be managed carefully.** - -The `OmitPolicy` top-level key in the [configuration](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the IDs of policies that should be excluded from the ScubaGear report. Excluded policies will show up as "Omitted" in the HTML report and will be colored gray. +The `OmitPolicy` top-level key in the [configuration](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the IDs of policies that should be excluded from the ScubaGear report. Excluded policies will show up as "Omitted" in the HTML report and will be colored gray. Exclusions must only be used if they are approved within an organization's security risk acceptance process. **Exclusions can introduce blind spots to your system and must be managed carefully.** For each exclusion, the config allows you to indicate the following: - `Rationale`: The reason the policy should be excluded from the report. This value will be displayed in the "Details" column of the report. ScubaGear will output a warning if no rationale is provided. From b0954a092a146eaa4c6b86b3321096a58ef195a9 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 14:49:37 -0700 Subject: [PATCH 15/22] Remove Test-Contains function --- .../Modules/CreateReport/CreateReport.psm1 | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index af18d98e98..1e695a3d56 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -301,28 +301,6 @@ function New-Report { $ReportSummary } -function Test-Contains { - <# - .Description - Tests if a custom PowerShell object contains a given key. - .Functionality - Internal - #> - [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [PSCustomObject] - $Object, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string] - $Key - ) - $Object.psobject.properties.name -Contains $Key -} - function Get-OmissionState { <# .Description @@ -343,10 +321,10 @@ function Get-OmissionState { $ControlId ) $Omit = $false - if (Test-Contains $Config "OmitPolicy") { - if (Test-Contains $Config.OmitPolicy $ControlId) { + if ($Config.psobject.properties.name -Contains "OmitPolicy") { + if ($Config.OmitPolicy.psobject.properties.name -Contains $ControlId) { # The config indicates the control should be omitted - if (Test-Contains $Config.OmitPolicy.$($ControlId) "Expiration") { + if ($Config.OmitPolicy.$($ControlId).psobject.properties.name -Contains "Expiration") { # An expiration date for the omission expiration was provided. Evaluate the date # to see if the control should still be omitted. if ($Config.OmitPolicy.$($ControlId).Expiration -eq "") { From a88f211009e05bcdba2b1af00fa9429c184cec49 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Fri, 5 Jul 2024 15:03:42 -0700 Subject: [PATCH 16/22] Add pester tests relevent to policy exclusion --- .../CreateReport/Get-OmissionState.Tests.ps1 | 102 ++++++++++++++++++ .../PowerShell/Support/New-Config.Tests.ps1 | 31 ++++++ 2 files changed, 133 insertions(+) create mode 100644 PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 new file mode 100644 index 0000000000..bcfbf1cddd --- /dev/null +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 @@ -0,0 +1,102 @@ +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../../../../Modules/CreateReport') + +InModuleScope CreateReport { + Describe -Tag CreateReport -Name 'Get-OmissionState' { + BeforeAll { + Mock -CommandName Write-Warning {} + } + + Context "When no expiration date is provided" { + It 'Returns false if the policy ID is not in the config' { + # If the policy is not in the config, the expriation date is N/A and the function + # should mark the policy as not omitted. + $Config = [PSCustomObject]@{ + "OmitPolicy" = [PSCustomObject]@{ + "MS.EXO.1.1v1" = [PSCustomObject]@{ "Rationale" = "Example rationale" } + } + } + $Result = Get-OmissionState $Config "MS.DEFENDER.1.1v1" + $Result | Should -Be $false + Should -Invoke -CommandName Write-Warning -Exactly -Times 0 + } + + It 'Returns true if the policy ID is in the config' { + # If the policy is in the config and the expriation date is not provided, as the + # expiration date is optional, the function should mark the policy as omitted. + $Config = [PSCustomObject]@{ + "OmitPolicy" = [PSCustomObject]@{ + "MS.DEFENDER.1.1v1" = [PSCustomObject]@{ "Rationale" = "Example rationale" } + } + } + $Result = Get-OmissionState $Config "MS.DEFENDER.1.1v1" + $Result | Should -Be $true + Should -Invoke -CommandName Write-Warning -Exactly -Times 0 + } + } + + Context "When an expiration date is provided" { + BeforeAll { + Mock -CommandName Get-Date { + # Modify the Get-Date function so that it returns a fixed date when + # no date is provided, instead of the current time. + if ($null -eq $Date) { + Get-Date -Date "2024-01-02" + } + else { + # If a specific date is requested, operate as normal + $Date + } + } + } + + It 'Returns true if the expiration is in the future' { + # If the date is in the future, the function should still mark the + # policy as not omitted. + $Config = [PSCustomObject]@{ + "OmitPolicy" = [PSCustomObject]@{ + "MS.DEFENDER.1.1v1" = [PSCustomObject]@{ + "Rationale" = "Example rationale"; + "Expiration" = "2024-01-03" } + } + } + $Result = Get-OmissionState $Config "MS.DEFENDER.1.1v1" + $Result | Should -Be $true + Should -Invoke -CommandName Write-Warning -Exactly -Times 0 + } + + It 'Returns false and warns if the expiration is in the past' { + # If the date is in the past, the functions should warn the user + # and mark the policy as not omitted. + $Config = [PSCustomObject]@{ + "OmitPolicy" = [PSCustomObject]@{ + "MS.DEFENDER.1.1v1" = [PSCustomObject]@{ + "Rationale" = "Example rationale"; + "Expiration" = "2024-01-01" } + } + } + $Result = Get-OmissionState $Config "MS.DEFENDER.1.1v1" + $Result | Should -Be $false + Should -Invoke -CommandName Write-Warning -Exactly -Times 1 + } + + It 'Returns false and warns if the expiration is malformed' { + # The functions should recognize that the date is malformed, warn the user, + # and mark the policy as not omitted. + $Config = [PSCustomObject]@{ + "OmitPolicy" = [PSCustomObject]@{ + "MS.DEFENDER.1.1v1" = [PSCustomObject]@{ + "Rationale" = "Example rationale"; + "Expiration" = "bad date" } + } + } + $Result = Get-OmissionState $Config "MS.DEFENDER.1.1v1" + $Result | Should -Be $false + Should -Invoke -CommandName Write-Warning -Exactly -Times 1 + } + } + } + + AfterAll { + Remove-Module CreateReport -ErrorAction SilentlyContinue + } +} diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 index 5e8ce0e7b4..595186eb35 100644 --- a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 @@ -47,6 +47,37 @@ InModuleScope Support { Test-Path -Path "$($TestPath)/SampleConfig.yaml" -PathType leaf | Should -Be $true } + + Context "When policy IDs are provided in the OmitPolicy parameter" { + It 'It reminds users to manually add the rationales' { + # Should warn once to for the reminder to manually add the rationales + $OmitArgs = $CMDArgs + $OmitArgs['OmitPolicy'] = @("MS.DEFENDER.1.1v1", "MS.DEFENDER.1.2v1") + { New-Config @OmitArgs } | Should -Not -Throw + Should -Invoke -CommandName Write-Warning -Exactly -Times 1 + Test-Path -Path "$($TestPath)/SampleConfig.yaml" -PathType leaf | Should -Be $true + } + + It 'Warns for malformed policy IDs' { + # The function should recognize that the policy ID does not match the expected + # format and give an additional warning for this + $OmitArgs = $CMDArgs + $OmitArgs['OmitPolicy'] = @("MS.DEFENDER1.1v1") + { New-Config @OmitArgs } | Should -Not -Throw + Should -Invoke -CommandName Write-Warning -Exactly -Times 2 + Test-Path -Path "$($TestPath)/SampleConfig.yaml" -PathType leaf | Should -Be $true + } + + It 'Warns for unexpected product in the policy ID' { + # The function should recognize that EXAMPLE is not a valid product + # and give an additional warning for this + $OmitArgs = $CMDArgs + $OmitArgs['OmitPolicy'] = @("MS.EXAMPLE.1.1v1", "MS.DEFENDER.1.2v1") + { New-Config @OmitArgs } | Should -Not -Throw + Should -Invoke -CommandName Write-Warning -Exactly -Times 2 + Test-Path -Path "$($TestPath)/SampleConfig.yaml" -PathType leaf | Should -Be $true + } + } } AfterAll { From 21f7b7d3dd9482a2b8d019c4204bba7aafddb05c Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Thu, 11 Jul 2024 14:15:07 -0700 Subject: [PATCH 17/22] Revise example config --- .../Sample-Config-Files/omit_policies.yaml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml index 0b69160180..5a277f41b6 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml @@ -8,7 +8,6 @@ Description: | ProductNames: - exo - teams - - defender M365Environment: commercial OPAPath: . LogIn: true @@ -18,20 +17,16 @@ OutFolderName: M365BaselineConformance OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports +CertificateThumbPrint: "51E15BC936056EF821CE6728364F057651260074" +AppID: "76e2fe6c-af3f-49f7-a6aa-65d53c58c91b" +Organization: "cisaent.onmicrosoft.com" OmitPolicy: MS.EXO.2.2v1: Rationale: "Known false positive; our SPF policy currently cannot to be retrieved via ScubaGear due to a split horizon setup but is available publicly." Expiration: "2023-12-31" MS.TEAMS.6.1v1: - Rationale: &DefenderRationale "Service provided via Microsoft M365 Defender." + Rationale: &DLPRationale "The DLP capability required for Teams is implemented by third party product, [x], + which ScubaGear does not have the ability to check." MS.TEAMS.6.2v1: - Rationale: *DefenderRationale - MS.TEAMS.7.1v1: - Rationale: *DefenderRationale - MS.TEAMS.7.2v1: - Rationale: *DefenderRationale - MS.TEAMS.8.1v1: - Rationale: *DefenderRationale - MS.TEAMS.8.2v1: - Rationale: *DefenderRationale + Rationale: *DLPRationale From ee4cb3dbf432a4c4d9b6c31cfe920effa96515b3 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Thu, 11 Jul 2024 14:18:11 -0700 Subject: [PATCH 18/22] Revise config try 2 --- PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml index 5a277f41b6..967c790fbe 100644 --- a/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml +++ b/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml @@ -17,9 +17,6 @@ OutFolderName: M365BaselineConformance OutProviderFileName: ProviderSettingsExport OutRegoFileName: TestResults OutReportName: BaselineReports -CertificateThumbPrint: "51E15BC936056EF821CE6728364F057651260074" -AppID: "76e2fe6c-af3f-49f7-a6aa-65d53c58c91b" -Organization: "cisaent.onmicrosoft.com" OmitPolicy: MS.EXO.2.2v1: Rationale: "Known false positive; our SPF policy currently cannot to be retrieved via ScubaGear due to a split From 1c1b7c9b4430948acf6f757ddee40cfae27fc6e4 Mon Sep 17 00:00:00 2001 From: Ted Kolovos <107076927+tkol2022@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:08:41 -0400 Subject: [PATCH 19/22] changed language to use term "omitted" instead of "excluded" not to confuse with AAD exclusions in a different section of the documentation --- docs/configuration/configuration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 1b58e650d6..68fa17dd3e 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -92,14 +92,14 @@ New-Config ## Omit Policies -In some cases, it may be appropriate to exclude policies from ScubaGear evaluation. For example: -- False positives due to a policy being implemented via a third-party service -- Policies not applicable to your organization (e.g., MS.EXO.4.3v1, which is only applicable to federal, executive branch, departments and agencies) +In some cases, it may be appropriate to omit specific policies from ScubaGear evaluation. For example: +- When a policy is implemented by a third-party service that ScubaGear does not audit +- When a policy is not applicable to your organization (e.g., policy MS.EXO.4.3v1 is only applicable to federal, executive branch, departments and agencies) -The `OmitPolicy` top-level key in the [configuration](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the IDs of policies that should be excluded from the ScubaGear report. Excluded policies will show up as "Omitted" in the HTML report and will be colored gray. Exclusions must only be used if they are approved within an organization's security risk acceptance process. **Exclusions can introduce blind spots to your system and must be managed carefully.** +The `OmitPolicy` top-level key in the [ScubaGear configuration file](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the policies that should be omitted from the ScubaGear report. Omitted policies will show up as "Omitted" in the HTML report and will be colored gray. Omitting policies must only be done if the omissions are approved within an organization's security risk management process. **Exercise care when omitting policies because this can inadvertently introduce blind spots when assessing your system.** -For each exclusion, the config allows you to indicate the following: -- `Rationale`: The reason the policy should be excluded from the report. This value will be displayed in the "Details" column of the report. ScubaGear will output a warning if no rationale is provided. +For each omitted policy, the config file allows you to indicate the following: +- `Rationale`: The reason the policy should be omitted from the report. This value will be displayed in the "Details" column of the report. ScubaGear will output a warning if no rationale is provided. - `Expiration`: Optional. A date after which the policy should no longer be omitted from the report. The expected format is yyyy-mm-dd. Policy omissions can be provided to the `New-Config` function as a comma-separated list of policy IDs under the `OmitPolicy` parameter. However, the `New-Config` function does not allow you to specify the rationales or expiration dates via the commandline; these must be manually entered into the resulting config file. From 9062ab8b738ef0d548dbc7e24e0c1b1fb081e45e Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Mon, 15 Jul 2024 09:00:15 -0700 Subject: [PATCH 20/22] Revise config md --- docs/configuration/configuration.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 68fa17dd3e..462f6857dd 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -96,14 +96,12 @@ In some cases, it may be appropriate to omit specific policies from ScubaGear ev - When a policy is implemented by a third-party service that ScubaGear does not audit - When a policy is not applicable to your organization (e.g., policy MS.EXO.4.3v1 is only applicable to federal, executive branch, departments and agencies) -The `OmitPolicy` top-level key in the [ScubaGear configuration file](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml) allows the user to specify the policies that should be omitted from the ScubaGear report. Omitted policies will show up as "Omitted" in the HTML report and will be colored gray. Omitting policies must only be done if the omissions are approved within an organization's security risk management process. **Exercise care when omitting policies because this can inadvertently introduce blind spots when assessing your system.** +The `OmitPolicy` top-level key, shown in this [example ScubaGear configuration file](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml), allows the user to specify the policies that should be omitted from the ScubaGear report. Omitted policies will show up as "Omitted" in the HTML report and will be colored gray. Omitting policies must only be done if the omissions are approved within an organization's security risk management process. **Exercise care when omitting policies because this can inadvertently introduce blind spots when assessing your system.** For each omitted policy, the config file allows you to indicate the following: - `Rationale`: The reason the policy should be omitted from the report. This value will be displayed in the "Details" column of the report. ScubaGear will output a warning if no rationale is provided. - `Expiration`: Optional. A date after which the policy should no longer be omitted from the report. The expected format is yyyy-mm-dd. -Policy omissions can be provided to the `New-Config` function as a comma-separated list of policy IDs under the `OmitPolicy` parameter. However, the `New-Config` function does not allow you to specify the rationales or expiration dates via the commandline; these must be manually entered into the resulting config file. - ## Product-specific Configuration Config files can include a top-level level key for a given product whose values are related to that specific product. For example, look for the value of `Defender` in this [Defender config file](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Sample-Config-Files/defender-config.yaml). Currently, only Entra ID and Defender use this extra configuration. From 7f0e696c460ba623a79a73f9b4171e7ddbf3cfe7 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Mon, 15 Jul 2024 14:15:09 -0700 Subject: [PATCH 21/22] Warn for bad IDs in the OmitPolicy key in config --- .../Modules/ScubaConfig/ScubaConfig.psm1 | 23 ++++++++++ .../CreateReport/Get-OmissionState.Tests.ps1 | 14 ++++++ .../ScubaConfigLoadConfig.Tests.ps1 | 44 +++++++++++++++++-- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/PowerShell/ScubaGear/Modules/ScubaConfig/ScubaConfig.psm1 b/PowerShell/ScubaGear/Modules/ScubaConfig/ScubaConfig.psm1 index 32f17d34d2..0d5b5904b3 100644 --- a/PowerShell/ScubaGear/Modules/ScubaConfig/ScubaConfig.psm1 +++ b/PowerShell/ScubaGear/Modules/ScubaConfig/ScubaConfig.psm1 @@ -56,6 +56,29 @@ class ScubaConfig { $this.SetParameterDefaults() [ScubaConfig]::_IsLoaded = $true + # If OmitPolicy was included in the config file, validate the policy IDs included there. + if ($this.Configuration.ContainsKey("OmitPolicy")) { + foreach ($Policy in $this.Configuration.OmitPolicy.Keys) { + if (-not ($Policy -match "^ms\.[a-z]+\.[0-9]+\.[0-9]+v[0-9]+$")) { + # Note that -match is a case insensitive match + # Note that the regex does not validate the product name, this will be done later + $Warning = "Config file indicates omitting $Policy, but $Policy is not a valid control ID. " + $Warning += "Expected format is 'MS.[PRODUCT].[GROUP].[NUMBER]v[VERSION]', " + $Warning += "e.g., 'MS.DEFENDER.1.1v1'. Control will not be omitted." + Write-Warning $Warning + Continue + } + $Product = ($Policy -Split "\.")[1] + # Here's where the product name is validated + if (-not ($this.Configuration.ProductNames -Contains $Product)) { + $Warning = "Config file indicates omitting $Policy, but $Product is not one of the products " + $Warning += "specified in the ProductNames parameter. Control will not be omitted." + Write-Warning $Warning + Continue + } + } + } + return [ScubaConfig]::_IsLoaded } diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 index bcfbf1cddd..48dfc75bec 100644 --- a/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Get-OmissionState.Tests.ps1 @@ -93,6 +93,20 @@ InModuleScope CreateReport { $Result | Should -Be $false Should -Invoke -CommandName Write-Warning -Exactly -Times 1 } + + It 'Returns false if the ID is malformed' { + # The functions should recognize that the ID is malformed + # and mark the policy as not omitted. + $Config = [PSCustomObject]@{ + "OmitPolicy" = [PSCustomObject]@{ + "MSDEFENDER.1.1v1" = [PSCustomObject]@{ + "Rationale" = "Example rationale"; + } + } + } + $Result = Get-OmissionState $Config "MS.DEFENDER.1.1v1" + $Result | Should -Be $false + } } } diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/ScubaConfig/ScubaConfigLoadConfig.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/ScubaConfig/ScubaConfigLoadConfig.Tests.ps1 index a2963524aa..0ffb372c69 100644 --- a/PowerShell/ScubaGear/Testing/Unit/PowerShell/ScubaConfig/ScubaConfigLoadConfig.Tests.ps1 +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/ScubaConfig/ScubaConfigLoadConfig.Tests.ps1 @@ -2,11 +2,12 @@ using module '..\..\..\..\Modules\ScubaConfig\ScubaConfig.psm1' InModuleScope ScubaConfig { Describe -tag "Utils" -name 'ScubaConfigLoadConfig' { + BeforeAll { + Mock -CommandName Write-Warning {} + function Get-ScubaDefault {throw 'this will be mocked'} + Mock -ModuleName ScubaConfig Get-ScubaDefault {"."} + } context 'Handling repeated LoadConfig invocations' { - BeforeAll { - function Get-ScubaDefault {throw 'this will be mocked'} - Mock -ModuleName ScubaConfig Get-ScubaDefault {"."} - } It 'Load valid config file followed by another'{ $cfg = [ScubaConfig]::GetInstance() # Load the first file and check the ProductNames value. @@ -25,10 +26,45 @@ InModuleScope ScubaConfig { } [ScubaConfig]::GetInstance().LoadConfig($PSCommandPath) | Should -BeTrue $cfg.Configuration.ProductNames | Should -Be 'exo' + Should -Invoke -CommandName Write-Warning -Exactly -Times 0 } AfterAll { [ScubaConfig]::ResetInstance() } } + context "Handling policy omissions" { + It 'Does not warn for proper control IDs' { + function global:ConvertFrom-Yaml { + @{ + ProductNames=@('exo'); + OmitPolicy=@{"MS.EXO.1.1v1"=@{"Rationale"="Example rationale"}} + } + } + [ScubaConfig]::GetInstance().LoadConfig($PSCommandPath) | Should -BeTrue + Should -Invoke -CommandName Write-Warning -Exactly -Times 0 + } + + It 'Warns for malformed control IDs' { + function global:ConvertFrom-Yaml { + @{ + ProductNames=@('exo'); + OmitPolicy=@{"MSEXO.1.1v1"=@{"Rationale"="Example rationale"}} + } + } + [ScubaConfig]::GetInstance().LoadConfig($PSCommandPath) | Should -BeTrue + Should -Invoke -CommandName Write-Warning -Exactly -Times 1 + } + + It 'Warns for control IDs not encompassed by ProductNames' { + function global:ConvertFrom-Yaml { + @{ + ProductNames=@('exo'); + OmitPolicy=@{"MS.Gmail.1.1v1"=@{"Rationale"="Example rationale"}} + } + } + [ScubaConfig]::GetInstance().LoadConfig($PSCommandPath) | Should -BeTrue + Should -Invoke -CommandName Write-Warning -Exactly -Times 1 + } + } } } From 640bc6096406142aeba8624a478babe45a60da82 Mon Sep 17 00:00:00 2001 From: Alden Hilton Date: Mon, 15 Jul 2024 14:40:10 -0700 Subject: [PATCH 22/22] Changed disabled to omitted --- PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 1e695a3d56..4b6ed47e25 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -120,7 +120,7 @@ function New-Report { "Requirement"=$Control.Value "Result"= "Omitted" "Criticality"= $Test.Criticality - "Details"= "Test disabled by user. $($OmitRationale)" + "Details"= "Test omitted by user. $($OmitRationale)" } continue }