Src/Private/Get-AbrExoTransportRules.ps1
|
function Get-AbrExoTransportRules { <# .SYNOPSIS Documents Exchange Online transport rules (mail flow rules) with ACSC E8 and CIS compliance assessments. .NOTES Version: 0.1.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Exchange Online Transport Rules for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'TransportRules' } process { Section -Style Heading2 'Transport Rules' { Paragraph "The following section documents the transport rules configured in tenant $TenantId." BlankLine $RulesBypassingSpamCount = 0 $ExternalForwardingRuleCount = 0 try { Write-Host " - Retrieving transport rules..." $TransportRules = Get-TransportRule -ErrorAction Stop | Sort-Object Priority $TotalRules = @($TransportRules).Count if ($TransportRules) { # Identify risk categories $SpamBypassRules = @($TransportRules | Where-Object { $_.SetSCL -eq -1 }) $ExternalForwardRules = @($TransportRules | Where-Object { $_.RedirectMessageTo -or ($_.AddToRecipients -and $_.BlindCopyTo) } | Where-Object { $_.RouteMessageOutboundRequiresTls -ne $true -or $_.RouteMessageOutboundConnector }) # Narrow to rules that actually forward externally $ExternalForwardRules = @($TransportRules | Where-Object { ($_.RedirectMessageTo -join ',') -notmatch $script:TenantDomain -and ($_.RedirectMessageTo.Count -gt 0) }) $RulesBypassingSpamCount = $SpamBypassRules.Count $ExternalForwardingRuleCount = $ExternalForwardRules.Count Paragraph "Tenant $TenantId has $TotalRules transport rule(s) configured. $RulesBypassingSpamCount rule(s) bypass spam filtering (SCL -1). $ExternalForwardingRuleCount rule(s) redirect email to external destinations." BlankLine #region Transport Rule Summary Table $RuleObj = [System.Collections.ArrayList]::new() foreach ($Rule in $TransportRules) { # Derive a human-readable action summary $ActionSummary = [System.Collections.ArrayList]::new() if ($Rule.SetSCL -eq -1) { $null = $ActionSummary.Add('Bypass Spam (SCL -1)') } if ($Rule.DeleteMessage) { $null = $ActionSummary.Add('Delete Message') } if ($Rule.RejectMessageEnhancedStatusCode) { $null = $ActionSummary.Add('Reject') } if ($Rule.RedirectMessageTo) { $null = $ActionSummary.Add("Redirect to $($Rule.RedirectMessageTo -join ', ')") } if ($Rule.BlindCopyTo) { $null = $ActionSummary.Add("BCC $($Rule.BlindCopyTo -join ', ')") } if ($Rule.AddToRecipients) { $null = $ActionSummary.Add("Add Recipient") } if ($Rule.PrependSubject) { $null = $ActionSummary.Add('Prepend Subject') } if ($Rule.ApplyHtmlDisclaimerText) { $null = $ActionSummary.Add('Apply Disclaimer') } if ($Rule.SetHeaderName) { $null = $ActionSummary.Add("Set Header $($Rule.SetHeaderName)") } if ($Rule.ApplyOME) { $null = $ActionSummary.Add('Apply OME Encryption') } if ($Rule.RouteMessageOutboundConnector) { $null = $ActionSummary.Add("Route via Connector: $($Rule.RouteMessageOutboundConnector)") } if ($ActionSummary.Count -eq 0) { $null = $ActionSummary.Add('Other') } $ruleInObj = [ordered] @{ 'Priority' = $Rule.Priority 'Rule Name' = $Rule.Name 'State' = $Rule.State 'Mode' = $Rule.Mode 'Action Summary' = $ActionSummary -join '; ' 'Comments' = if ($Rule.Comments) { $Rule.Comments } else { '--' } } $RuleObj.Add([pscustomobject]$ruleInObj) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.TransportRules) { $null = ($RuleObj | Where-Object { $_.'Action Summary' -like '*Bypass Spam*' } | Set-Style -Style Warning | Out-Null) $null = ($RuleObj | Where-Object { $_.'Action Summary' -like '*Redirect*' -and $_.'Action Summary' -notlike "*$($script:TenantDomain)*" } | Set-Style -Style Critical | Out-Null) $null = ($RuleObj | Where-Object { $_.State -eq 'Disabled' } | Set-Style -Style Warning | Out-Null) } }) $RuleTableParams = @{ Name = "Transport Rules - $TenantId"; List = $false; ColumnWidths = 6, 22, 8, 8, 40, 16 } if ($Report.ShowTableCaptions) { $RuleTableParams['Caption'] = "- $($RuleTableParams.Name)" } $RuleObj | Table @RuleTableParams $script:ExcelSheets['Transport Rules'] = $RuleObj #endregion #region Spam Bypass Rules Detail if ($SpamBypassRules.Count -gt 0) { Section -Style Heading3 'Rules Bypassing Spam Filtering (SCL -1)' { Paragraph "WARNING: The following $($SpamBypassRules.Count) rule(s) set the spam confidence level to -1, bypassing all anti-spam filtering. These should be reviewed carefully." BlankLine $BypassObj = [System.Collections.ArrayList]::new() foreach ($Rule in $SpamBypassRules) { $condSummary = [System.Collections.ArrayList]::new() if ($Rule.From) { $null = $condSummary.Add("From: $($Rule.From -join ', ')") } if ($Rule.FromMemberOf) { $null = $condSummary.Add("From Group: $($Rule.FromMemberOf -join ', ')") } if ($Rule.SenderDomainIs) { $null = $condSummary.Add("Sender Domain: $($Rule.SenderDomainIs -join ', ')") } if ($Rule.ReceivedFromIPRanges) { $null = $condSummary.Add("IP Range: $($Rule.ReceivedFromIPRanges -join ', ')") } if ($condSummary.Count -eq 0) { $null = $condSummary.Add('All Messages (NO CONDITION -- HIGH RISK)') } $bypassInObj = [ordered] @{ 'Rule Name' = $Rule.Name 'Priority' = $Rule.Priority 'State' = $Rule.State 'Condition(s)' = $condSummary -join '; ' } $BypassObj.Add([pscustomobject]$bypassInObj) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.TransportRules) { $null = ($BypassObj | Set-Style -Style Critical | Out-Null) } }) $BypassTableParams = @{ Name = "Spam Bypass Rules - $TenantId"; List = $false; ColumnWidths = 30, 8, 8, 54 } if ($Report.ShowTableCaptions) { $BypassTableParams['Caption'] = "- $($BypassTableParams.Name)" } $BypassObj | Table @BypassTableParams $script:ExcelSheets['Spam Bypass Rules'] = $BypassObj } } #endregion #region External Forwarding Rules Detail if ($ExternalForwardRules.Count -gt 0) { Section -Style Heading3 'Rules Redirecting to External Recipients' { Paragraph "WARNING: The following $($ExternalForwardRules.Count) rule(s) redirect or forward email to external recipients. These represent potential data exfiltration paths." BlankLine $FwdRuleObj = [System.Collections.ArrayList]::new() foreach ($Rule in $ExternalForwardRules) { $fwdRuleInObj = [ordered] @{ 'Rule Name' = $Rule.Name 'Priority' = $Rule.Priority 'State' = $Rule.State 'Redirect To' = $Rule.RedirectMessageTo -join ', ' } $FwdRuleObj.Add([pscustomobject]$fwdRuleInObj) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.TransportRules) { $null = ($FwdRuleObj | Set-Style -Style Critical | Out-Null) } }) $FwdRuleTableParams = @{ Name = "External Forwarding Rules - $TenantId"; List = $false; ColumnWidths = 35, 8, 8, 49 } if ($Report.ShowTableCaptions) { $FwdRuleTableParams['Caption'] = "- $($FwdRuleTableParams.Name)" } $FwdRuleObj | Table @FwdRuleTableParams $script:ExcelSheets['External Forward Rules'] = $FwdRuleObj } } #endregion } else { Paragraph "No transport rules are currently configured in tenant $TenantId." } } catch { Write-ExoError 'TransportRules' "Unable to retrieve transport rules: $($_.Exception.Message)" Paragraph "Unable to retrieve transport rule data: $($_.Exception.Message)" } BlankLine Paragraph "ACSC Essential Eight Assessment" BlankLine $e8Vars = @{ RulesBypassingSpamCount = $RulesBypassingSpamCount ExternalForwardingRuleCount = $ExternalForwardingRuleCount } $e8Checks = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoE8Checks 'TransportRules') -Framework 'E8' -CallerVariables $e8Vars New-AbrExoE8AssessmentTable -Checks $e8Checks -Name 'Transport Rules' -TenantId $TenantId if ($e8Checks) { foreach ($row in $e8Checks) { $null = $script:E8AllChecks.Add([pscustomobject]@{ Section = 'Transport Rules' ML = $row.ML Control = $row.Control Status = $row.Status Detail = $row.Detail }) } } BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment" BlankLine $cisVars = @{ RulesBypassingSpamCount = $RulesBypassingSpamCount ExternalForwardingRuleCount = $ExternalForwardingRuleCount } $cisChecks = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoCISChecks 'TransportRules') -Framework 'CIS' -CallerVariables $cisVars New-AbrExoCISAssessmentTable -Checks $cisChecks -Name 'Transport Rules' -TenantId $TenantId if ($cisChecks) { foreach ($row in $cisChecks) { $null = $script:CISAllChecks.Add([pscustomobject]@{ Section = 'Transport Rules' CISControl = $row.CISControl Level = $row.Level Status = $row.Status Detail = $row.Detail }) } } } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'TransportRules' } } |