From 4e12eae6a91331371c11fda87b7f75322833f030 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:05:28 -0600 Subject: [PATCH] add: Tests for 2.17 --- CHANGELOG.md | 4 +- source/Private/Get-CISExoOutput.ps1 | 9 +- source/Private/Get-PhishPolicyCompliance.ps1 | 45 ++++++++ source/Private/Get-ScopeOverlap.ps1 | 48 ++++++++ source/tests/Test-AntiPhishingPolicy4.ps1 | 114 +++++++++++++++++++ 5 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 source/Private/Get-PhishPolicyCompliance.ps1 create mode 100644 source/Private/Get-ScopeOverlap.ps1 create mode 100644 source/tests/Test-AntiPhishingPolicy4.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index c0eab5c..498a574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ The format is based on and uses the types of changes according to [Keep a Change - Test Definition Placeholders - Steps to function to account for new logic and create an updated test definition object when version 4.0.0 is selected. - Test-AdministrativeAccountCompliance4 function for v4.0.0 rec# 1.1.1 test. -- Updated Get-CISMgOutput function to include the new test definition case for 1.1.1. +- Updated Get-CISMgOutput function to include the new test definition case for 1.1.1,1.1.4 and 2.1.7. - New public function for generating version specific lists of recommendation numbers. - Check in main public function to check for 4.0.0 rec numbers when 3.0.0 is selected as the M365 benchmark version. - Rec numbers to include and exclude rec numbers for version 4.0.0 so the 'validate set' works correctly. +- Get-PhishPolicyCompliance and Get-ScopeOverlap private functions for 2.1.7 v4. +- Test-PhishPolicyCompliance4 function for 2.1.7 v4. ### Fixed diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index 0ea5f85..fb053af 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -339,7 +339,14 @@ function Get-CISExoOutput { ) #> $antiPhishPolicies = Get-AntiPhishPolicy - return $antiPhishPolicies + if ($script:Version400) { + Write-Verbose 'Retrieving associated AntiPhishRules...' + $antiPhishRules = Get-AntiPhishRule + return $antiPhishPolicies, $antiPhishRules + } + else { + return $antiPhishPolicies + } } '2.1.9' { # Test-EnableDKIM.ps1 diff --git a/source/Private/Get-PhishPolicyCompliance.ps1 b/source/Private/Get-PhishPolicyCompliance.ps1 new file mode 100644 index 0000000..4208aa9 --- /dev/null +++ b/source/Private/Get-PhishPolicyCompliance.ps1 @@ -0,0 +1,45 @@ +function Get-PhishPolicyCompliance { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [PSCustomObject]$Policy + ) + # Define the compliance criteria for an anti-phishing policy + $complianceCriteria = @{ + Enabled = $true # Policy must be enabled + EnableTargetedUserProtection = $true # Targeted user protection must be enabled + EnableOrganizationDomainsProtection = $true # Organization domains protection must be enabled + EnableMailboxIntelligence = $true # Mailbox intelligence must be enabled + EnableMailboxIntelligenceProtection = $true # Mailbox intelligence protection must be enabled + EnableSpoofIntelligence = $true # Spoof intelligence must be enabled + TargetedUserProtectionAction = 'Quarantine' # Actions for targeted user protection must be 'Quarantine' + TargetedDomainProtectionAction = 'Quarantine' # Actions for targeted domain protection must be 'Quarantine' + MailboxIntelligenceProtectionAction = 'Quarantine' # Actions for mailbox intelligence protection must be 'Quarantine' + EnableFirstContactSafetyTips = $true # First contact safety tips must be enabled + EnableSimilarUsersSafetyTips = $true # Similar users safety tips must be enabled + EnableSimilarDomainsSafetyTips = $true # Similar domains safety tips must be enabled + EnableUnusualCharactersSafetyTips = $true # Unusual characters safety tips must be enabled + HonorDmarcPolicy = $true # Honor DMARC policy must be enabled + } + # Initialize compliance state and a list to track non-compliance reasons + $isCompliant = $true + $nonCompliantReasons = @() + # Iterate through the compliance criteria and check each property of the policy + foreach ($key in $complianceCriteria.Keys) { + if ($Policy.PSObject.Properties[$key] -and $Policy.$key -ne $complianceCriteria[$key]) { + $isCompliant = $false # Mark as non-compliant if the value doesn't match + $nonCompliantReasons += "$key`: Expected $($complianceCriteria[$key]), Found $($Policy.$key)" # Record the discrepancy + } + } + # Special case: Ensure PhishThresholdLevel is at least 3 + if ($Policy.PSObject.Properties['PhishThresholdLevel'] -and $Policy.PhishThresholdLevel -lt 3) { + $isCompliant = $false # Mark as non-compliant if threshold is below 3 + $nonCompliantReasons += "PhishThresholdLevel: Expected at least 3, Found $($Policy.PhishThresholdLevel)" # Record the issue + } + # Log the reasons for non-compliance if the policy is not compliant + if (-not $isCompliant) { + Write-Verbose "Policy $($Policy.Name) is not compliant. Reasons: $($nonCompliantReasons -join '; ')" + } + # Return whether the policy is compliant + return $isCompliant +} \ No newline at end of file diff --git a/source/Private/Get-ScopeOverlap.ps1 b/source/Private/Get-ScopeOverlap.ps1 new file mode 100644 index 0000000..74d98c3 --- /dev/null +++ b/source/Private/Get-ScopeOverlap.ps1 @@ -0,0 +1,48 @@ +function Get-ScopeOverlap { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [PSCustomObject]$Policy, # The primary policy whose scope we are evaluating + [Parameter(Mandatory = $true)] + [PSCustomObject[]]$OtherPolicies # A list of other policies to compare for scope overlap + ) + # Write a verbose message indicating the policy being evaluated for overlap + Write-Verbose "Checking for scope overlap with $($Policy.Name)..." + # Initialize variables to track overlap status and overlapping entities + $overlapDetected = $false # Tracks if any overlap is detected + $overlappingEntities = @() # Stores details of overlapping entities for logging + # Build the scope string of the current policy by concatenating users, groups, and domains + $policyScope = @( + $Policy.Users -join ',', # Users within the policy's scope + $Policy.Groups -join ',', # Groups within the policy's scope + $Policy.Domains -join ',' # Domains within the policy's scope + ) -join ',' # Combine all into a single string + # Iterate through each policy in the list of other policies + foreach ($otherPolicy in $OtherPolicies) { + if ($null -ne $otherPolicy) { # Skip null or empty policies + # Build the scope string for the other policy + $otherScope = @( + $otherPolicy.Users -join ',', # Users within the other policy's scope + $otherPolicy.Groups -join ',', # Groups within the other policy's scope + $otherPolicy.Domains -join ',' # Domains within the other policy's scope + ) -join ',' # Combine all into a single string + # Check if the current policy's scope matches any part of the other policy's scope + if ($policyScope -match $otherScope) { + $overlapDetected = $true # Mark overlap as detected + # Log overlapping entities for clarity + $overlappingEntities += @( + "Users: $($otherPolicy.Users)", + "Groups: $($otherPolicy.Groups)", + "Domains: $($otherPolicy.Domains)" + ) + Write-Verbose "Overlap detected between $($Policy.Name) and $($otherPolicy.Name)." # Log the overlap + } + } + } + # If overlap is detected, log the specific overlapping entities + if ($overlapDetected) { + Write-Verbose "Overlapping entities: $($overlappingEntities -join '; ')" # Log overlapping users, groups, or domains + } + # Return whether overlap was detected (true/false) + return $overlapDetected +} diff --git a/source/tests/Test-AntiPhishingPolicy4.ps1 b/source/tests/Test-AntiPhishingPolicy4.ps1 new file mode 100644 index 0000000..0185bf9 --- /dev/null +++ b/source/tests/Test-AntiPhishingPolicy4.ps1 @@ -0,0 +1,114 @@ +function Test-AntiPhishingPolicy4 { + [CmdletBinding()] + [OutputType([CISAuditResult])] + param () + begin { + # Set the record number and start the process + $RecNum = '2.1.7' + Write-Verbose "Running Test-AntiPhishingPolicy4 for $RecNum..." + } + process { + try { + # Step 1: Retrieve all anti-phishing policies and rules + Write-Verbose 'Retrieving all anti-phishing policies and rules...' + $antiPhishPolicies, $antiPhishRules = Get-CISExoOutput -Rec $RecNum + if ($null -eq $antiPhishPolicies -or $antiPhishPolicies.Count -eq 0) { + throw 'No Anti-Phishing policies found.' + } + # Initialize lists to track compliant and non-compliant policies and reasons for failures + $compliantPolicies = @() + $failureReasons = @() + $nonCompliantPolicies = @() + # Step 2: Evaluate strict and standard preset policies + Write-Verbose 'Evaluating strict and standard preset policies...' + $strictPolicy = $antiPhishPolicies | Where-Object { $_.Name -eq 'Strict Preset Security Policy' } + $standardPolicy = $antiPhishPolicies | Where-Object { $_.Name -eq 'Standard Preset Security Policy' } + $strictStandardCompliant = $false + foreach ($policy in @($strictPolicy, $standardPolicy)) { + if ($null -ne $policy) { + # Check if the strict or standard policy is compliant + $isCompliant = Get-PhishPolicyCompliance -policy $policy + if ($isCompliant) { + $strictStandardCompliant = $true + $compliantPolicies += $policy.Name + } else { + $nonCompliantPolicies += $policy.Name + } + } + } + # Step 3: Evaluate custom policies if strict and standard are not compliant + if (-not $strictStandardCompliant) { + Write-Verbose 'Evaluating custom policies for compliance...' + # Filter custom policies that match any rules in $antiPhishRules and sort by priority + $customPolicies = $antiPhishPolicies | Where-Object { $antiPhishRules.AntiPhishPolicy -contains $_.Name } + $customPolicies = $customPolicies | Sort-Object -Property { $antiPhishRules | Where-Object { $_.AntiPhishPolicy -eq $_.Name } | Select-Object -ExpandProperty Priority } + foreach ($policy in $customPolicies) { + # Check for scope overlap between custom policies and strict/standard policies + $scopeOverlap = Get-ScopeOverlap -Policy $policy -OtherPolicies @($strictPolicy, $standardPolicy) + if ($scopeOverlap) { + $failureReasons += "Custom policy $($policy.Name) overlaps with strict or standard preset policies." + $nonCompliantPolicies += $policy.Name + } else { + # Check if the custom policy is compliant + $isCompliant = Get-PhishPolicyCompliance -policy $policy + if ($isCompliant) { + $compliantPolicies += $policy.Name + } else { + $nonCompliantPolicies += $policy.Name + } + } + } + } + # Step 4: Evaluate the default policy if no compliant custom, strict, or standard policies + if ($compliantPolicies.Count -eq 0) { + Write-Verbose 'Evaluating default policy for compliance...' + $defaultPolicy = $antiPhishPolicies | Where-Object { $_.Name -eq 'Office365 AntiPhish Default' } + if ($null -ne $defaultPolicy) { + # Check for scope overlap between the default policy and other policies + $scopeOverlap = Get-ScopeOverlap -Policy $defaultPolicy -OtherPolicies @($strictPolicy, $standardPolicy, $antiPhishPolicies | Where-Object { $_.Name -ne 'Office365 AntiPhish Default' }) + if ($scopeOverlap) { + $failureReasons += "Default policy overlaps with other scoped policies." + $nonCompliantPolicies += $defaultPolicy.Name + } else { + # Check if the default policy is compliant + $isCompliant = Get-PhishPolicyCompliance -policy $defaultPolicy + if ($isCompliant) { + $compliantPolicies += $defaultPolicy.Name + } else { + $nonCompliantPolicies += $defaultPolicy.Name + } + } + } + } + # Step 5: Determine overall compliance + $isOverallCompliant = ($compliantPolicies.Count -gt 0) -and ($failureReasons.Count -eq 0) + # Step 6: Prepare result details + $resultDetails = if ($isOverallCompliant) { + # Prepare details for compliant policies + "Compliant Policies: $($compliantPolicies -join ', ')" + } + else { + # Prepare details for non-compliant policies and reasons + "Non-Compliant Policies: $($nonCompliantPolicies -join ', ')`nFailure Reasons:`n" + ($failureReasons -join "`n") + } + # Step 7: Prepare the audit result object + $params = @{ + Rec = $RecNum + Result = $isOverallCompliant + Status = if ($isOverallCompliant) { 'Pass' } else { 'Fail' } + Details = $resultDetails + FailureReason = if (-not $isOverallCompliant) { $failureReasons -join "`n" } else { 'None' } + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + # Handle errors and return the error result + Write-Error "An error occurred during the test $RecNum`: $_" + $auditResult = Get-TestError -LastError $_ -RecNum $RecNum + } + } + end { + # Return the audit result object + return $auditResult + } +} \ No newline at end of file