Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow policies to be omitted from ScubaGear #1200

Merged
merged 22 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
de17a1e
Add example config file for ignoring policies
adhilto Jun 6, 2024
33246ab
Added support for skipping policies to the report generation script.
adhilto Jun 7, 2024
c7eacc4
Modify report summary to show number of skips
adhilto Jun 10, 2024
55192fe
Correct bug with skip summary
adhilto Jun 10, 2024
e79bc3d
Remove trailing whitespace
adhilto Jun 10, 2024
50ddbdc
Rename skipped to omitted; color omitted gray; add new summary box fo…
adhilto Jun 20, 2024
6716977
Change 'skipped' to 'disabled'
adhilto Jun 21, 2024
b27be8b
Implement omission exiration date
adhilto Jul 4, 2024
844d201
Tweak config file
adhilto Jul 4, 2024
adb2939
Added support for policy omission to New-Config function
adhilto Jul 5, 2024
a057096
Make it so that the OmitPolicy config key isn't nested under the prod…
adhilto Jul 5, 2024
fa6a74a
Add documentation for omitting policies
adhilto Jul 5, 2024
791f830
Minor tweaks to the config md
adhilto Jul 5, 2024
403e51a
Merge two paragraphs
adhilto Jul 5, 2024
b0954a0
Remove Test-Contains function
adhilto Jul 5, 2024
a88f211
Add pester tests relevent to policy exclusion
adhilto Jul 5, 2024
21f7b7d
Revise example config
adhilto Jul 11, 2024
ee4cb3d
Revise config try 2
adhilto Jul 11, 2024
1c1b7c9
changed language to use term "omitted" instead of "excluded" not to c…
tkol2022 Jul 12, 2024
9062ab8
Revise config md
adhilto Jul 15, 2024
7f0e696
Warn for bad IDs in the OmitPolicy key in config
adhilto Jul 15, 2024
640bc60
Changed disabled to omitted
adhilto Jul 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ function New-Report {
"Warnings" = 0;
"Failures" = 0;
"Passes" = 0;
"Omits" = 0;
"Manual" = 0;
"Errors" = 0;
"Date" = $SettingsExport.date;
Expand All @@ -101,6 +102,29 @@ function New-Report {
$Test = $TestResults | Where-Object -Property PolicyId -eq $Control.Id

if ($null -ne $Test){
# 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 = $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."
}
else {
$OmitRationale = "`"$($OmitRationale)`""
}
$Fragment += [pscustomobject]@{
"Control ID"=$Control.Id
"Requirement"=$Control.Value
"Result"= "Omitted"
"Criticality"= $Test.Criticality
"Details"= "Test omitted by user. $($OmitRationale)"
}
continue
}

$MissingCommands = $Test.Commandlet | Where-Object {$SettingsExport."$($BaselineName)_successful_commands" -notcontains $_}

if ($MissingCommands.Count -gt 0) {
Expand Down Expand Up @@ -277,6 +301,72 @@ function New-Report {
$ReportSummary
}

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 ($Config.psobject.properties.name -Contains "OmitPolicy") {
if ($Config.OmitPolicy.psobject.properties.name -Contains $ControlId) {
# The config indicates the control should be omitted
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 "") {
# 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, 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
Expand Down
3 changes: 3 additions & 0 deletions PowerShell/ScubaGear/Modules/CreateReport/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 === "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)";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ footer {
display: inline-block;
padding: 0.313em;
border-radius: 0.313em;
min-width: 140px;
min-width: 100px;
text-align: center;
}

Expand Down
23 changes: 16 additions & 7 deletions PowerShell/ScubaGear/Modules/Orchestrator.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -1005,27 +1005,36 @@ function Invoke-ReportCreation {
$LinkPath = "$($IndividualReportFolderName)/$($BaselineName)Report.html"
$LinkClassName = '"individual_reports"' # uses no escape characters
$Link = "<a class=$($LinkClassName) href='$($LinkPath)'>$($FullName)</a>"

$PassesSummary = "<div class='summary pass'>$($Report.Passes) tests passed</div>"
$PassesSummary = "<div class='summary'></div>"
$WarningsSummary = "<div class='summary'></div>"
$FailuresSummary = "<div class='summary'></div>"
$BaselineURL = "<a href= `"https://github.com/cisagov/ScubaGear/blob/v$($ModuleVersion)/baselines`" target=`"_blank`"><h3 style=`"width: 100px;`">Baseline Documents</h3></a>"
$ManualSummary = "<div class='summary'></div>"
$ErrorSummary = "<div class='summary'></div>"
$OmitSummary = "<div class='summary'></div>"
$ErrorSummary = ""

if ($Report.Passes -gt 0) {
$Noun = Pluralize -SingularNoun "pass" -PluralNoun "passes" -Count $Report.Passes
$PassesSummary = "<div class='summary pass'>$($Report.Passes) $($Noun)</div>"
}

if ($Report.Warnings -gt 0) {
$Noun = Pluralize -SingularNoun "warning" -PluralNoun "warnings" -Count $Report.Warnings
$WarningsSummary = "<div class='summary warning'>$($Report.Warnings) $($Noun)</div>"
}

if ($Report.Failures -gt 0) {
$Noun = Pluralize -SingularNoun "test" -PluralNoun "tests" -Count $Report.Failures
$FailuresSummary = "<div class='summary failure'>$($Report.Failures) $($Noun) failed</div>"
$Noun = Pluralize -SingularNoun "failure" -PluralNoun "failures" -Count $Report.Failures
$FailuresSummary = "<div class='summary failure'>$($Report.Failures) $($Noun)</div>"
}

if ($Report.Manual -gt 0) {
$Noun = Pluralize -SingularNoun "check" -PluralNoun "checks" -Count $Report.Manual
$ManualSummary = "<div class='summary manual'>$($Report.Manual) manual $($Noun) needed</div>"
$ManualSummary = "<div class='summary manual'>$($Report.Manual) manual $($Noun)</div>"
}

if ($Report.Omits -gt 0) {
$OmitSummary = "<div class='summary manual'>$($Report.Omits) omitted</div>"
}

if ($Report.Errors -gt 0) {
Expand All @@ -1035,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]@{
Expand Down
23 changes: 23 additions & 0 deletions PowerShell/ScubaGear/Modules/ScubaConfig/ScubaConfig.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
56 changes: 55 additions & 1 deletion PowerShell/ScubaGear/Modules/Support/Support.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -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
#>
Expand Down Expand Up @@ -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])
Expand All @@ -825,6 +833,12 @@ function New-Config {
}
}

if ($config.Contains("OmitPolicy")) {
# We don't want to immediately save this parameter to the config, as it's not in the right
# format yet.
$config.Remove("OmitPolicy")
}

$CapExclusionNamespace = @(
"MS.AAD.1.1v1",
"MS.AAD.2.1v1",
Expand All @@ -849,6 +863,46 @@ 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
$config[$OmissionNamespace] = @{}

foreach ($Policy in $OmitPolicy) {
adhilto marked this conversation as resolved.
Show resolved Hide resolved
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
}
# 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
$config[$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])
Expand Down
29 changes: 29 additions & 0 deletions PowerShell/ScubaGear/Sample-Config-Files/omit_policies.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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 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:
- exo
- teams
M365Environment: commercial
OPAPath: .
LogIn: true
DisconnectOnExit: false
OutPath: .
OutFolderName: M365BaselineConformance
OutProviderFileName: ProviderSettingsExport
OutRegoFileName: TestResults
OutReportName: BaselineReports
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: &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: *DLPRationale
Loading
Loading