Src/Private/Get-AbrExoDKIMDMARC.ps1
|
function Get-AbrExoDKIMDMARC { <# .SYNOPSIS Documents DKIM signing configuration and DMARC DNS records for all Exchange Online accepted domains, 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 DKIM/DMARC configuration for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'DKIM_DMARC' } process { Section -Style Heading2 'DKIM & DMARC' { Paragraph "The following section documents DKIM signing and DMARC policy configuration for tenant $TenantId." BlankLine # Compliance variable defaults $DkimTotalDomainCount = 0 $DkimEnabledDomainCount = 0 $Dkim2048BitCount = 0 $DmarcDomainsTotal = 0 $DmarcDomainsConfigured = 0 $DmarcDomainsCoveredPct = 0 $DmarcEnforcedDomainCount = 0 $DmarcRuaConfiguredCount = 0 #region DKIM try { Write-Host " - Retrieving DKIM signing configuration..." $DkimConfigs = Get-DkimSigningConfig -ErrorAction Stop | Sort-Object Domain # Also get accepted domains for accurate total (DKIM configs only appear after setup) $AcceptedDomainsForDkim = @(Get-AcceptedDomain -ErrorAction SilentlyContinue | Where-Object { $_.DomainType -eq 'Authoritative' -and $_.DomainName -notlike '*.onmicrosoft.com' }) $DkimTotalDomainCount = if (@($DkimConfigs).Count -gt 0) { @($DkimConfigs).Count } else { $AcceptedDomainsForDkim.Count } $DkimEnabledDomainCount = @($DkimConfigs | Where-Object { $_.Enabled -eq $true }).Count $Dkim2048BitCount = @($DkimConfigs | Where-Object { $_.Enabled -eq $true -and $_.KeySize -eq 2048 }).Count Section -Style Heading3 'DKIM Signing Configuration' { if (@($DkimConfigs).Count -eq 0) { Paragraph "No DKIM signing configurations found. DKIM has not been explicitly configured for any domain in tenant $TenantId. To enable DKIM, visit the Microsoft 365 Defender portal at security.microsoft.com > Email & Collaboration > Policies & Rules > Threat Policies > Email Authentication Settings > DKIM." BlankLine Paragraph "Custom domains require DKIM to be explicitly enabled. The onmicrosoft.com domain uses Microsoft-managed DKIM signing by default." } else { Paragraph "DKIM signing configuration for $(@($DkimConfigs).Count) domain(s) in tenant $TenantId. $DkimEnabledDomainCount of $(@($DkimConfigs).Count) domain(s) have DKIM signing enabled." BlankLine $DkimObj = [System.Collections.ArrayList]::new() foreach ($Dkim in $DkimConfigs) { $dkimInObj = [ordered] @{ 'Domain' = $Dkim.Domain 'DKIM Enabled' = $Dkim.Enabled 'Key Size (bits)' = if ($Dkim.KeySize) { $Dkim.KeySize } else { 'Unknown' } 'Selector 1 Key' = if ($Dkim.Selector1PublicKey) { 'Published' } else { 'Not Published' } 'Selector 2 Key' = if ($Dkim.Selector2PublicKey) { 'Published' } else { 'Not Published' } 'Last Checked' = if ($Dkim.LastChecked) { $Dkim.LastChecked.ToString('yyyy-MM-dd') } else { 'Never' } 'Status' = if ($Dkim.Status) { $Dkim.Status } else { 'Unknown' } } $DkimObj.Add([pscustomobject](ConvertTo-HashToYN $dkimInObj)) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.DKIM) { $null = ($DkimObj | Where-Object { $_.'DKIM Enabled' -eq 'No' } | Set-Style -Style Critical | Out-Null) $null = ($DkimObj | Where-Object { $_.'Key Size (bits)' -ne '2048' -and $_.'DKIM Enabled' -eq 'Yes' } | Set-Style -Style Warning | Out-Null) $null = ($DkimObj | Where-Object { $_.'Selector 1 Key' -eq 'Not Published' -or $_.'Selector 2 Key' -eq 'Not Published' } | Set-Style -Style Warning | Out-Null) } }) $DkimTableParams = @{ Name = "DKIM Signing Configuration - $TenantId"; List = $false; ColumnWidths = 26, 10, 12, 12, 12, 14, 14 } if ($Report.ShowTableCaptions) { $DkimTableParams['Caption'] = "- $($DkimTableParams.Name)" } $DkimObj | Table @DkimTableParams $script:ExcelSheets['DKIM'] = $DkimObj } } } catch { Write-ExoError 'DKIM' "Unable to retrieve DKIM configuration: $($_.Exception.Message)" Paragraph "Unable to retrieve DKIM configuration: $($_.Exception.Message)" } #endregion #region DMARC - DNS Lookup try { Write-Host " - Checking DMARC DNS records..." $AcceptedDomains = Get-AcceptedDomain -ErrorAction Stop | Where-Object { $_.DomainType -eq 'Authoritative' } $DmarcDomainsTotal = @($AcceptedDomains).Count $DmarcObj = [System.Collections.ArrayList]::new() foreach ($Domain in $AcceptedDomains) { $DmarcRecord = $null $DmarcPolicy = 'None' $DmarcPct = 100 $DmarcRua = 'Not Set' $DmarcRuf = 'Not Set' $DmarcFound = $false $DmarcEnforced = $false try { $DnsResult = Resolve-DnsName -Name "_dmarc.$($Domain.DomainName)" -Type TXT -ErrorAction SilentlyContinue $DmarcTxt = ($DnsResult | Where-Object { $_.Strings -like 'v=DMARC1*' }).Strings if ($DmarcTxt) { $DmarcFound = $true $DmarcRecord = $DmarcTxt -join '' if ($DmarcRecord -match 'p=(\w+)') { $DmarcPolicy = $Matches[1] $DmarcEnforced = $DmarcPolicy -in @('quarantine', 'reject') } if ($DmarcRecord -match 'pct=(\d+)') { $DmarcPct = [int]$Matches[1] } if ($DmarcRecord -match 'rua=([^\s;]+)') { $DmarcRua = $Matches[1] } if ($DmarcRecord -match 'ruf=([^\s;]+)') { $DmarcRuf = $Matches[1] } } } catch { # DNS lookup failed -- treat as not found } if ($DmarcFound) { $DmarcDomainsConfigured++ } if ($DmarcEnforced) { $DmarcEnforcedDomainCount++ } if ($DmarcRua -ne 'Not Set') { $DmarcRuaConfiguredCount++ } $dmarcInObj = [ordered] @{ 'Domain' = $Domain.DomainName 'DMARC Record' = if ($DmarcFound) { 'Found' } else { 'Not Found' } 'Policy (p=)' = if ($DmarcFound) { $DmarcPolicy } else { '--' } 'Pct (%)' = if ($DmarcFound) { $DmarcPct } else { '--' } 'Aggregate (rua)' = $DmarcRua 'Forensic (ruf)' = $DmarcRuf 'Enforced' = $DmarcEnforced } $DmarcObj.Add([pscustomobject](ConvertTo-HashToYN $dmarcInObj)) | Out-Null } $DmarcDomainsCoveredPct = if ($DmarcDomainsTotal -gt 0) { [math]::Round(($DmarcDomainsConfigured / $DmarcDomainsTotal) * 100, 0) } else { 0 } Section -Style Heading3 'DMARC DNS Records' { Paragraph "DMARC record status for $DmarcDomainsTotal authoritative domain(s). $DmarcDomainsConfigured domain(s) have a DMARC record published ($DmarcDomainsCoveredPct% coverage). $DmarcEnforcedDomainCount domain(s) enforce quarantine or reject policy." BlankLine $null = (& { if ($HealthCheck.ExchangeOnline.DMARC) { $null = ($DmarcObj | Where-Object { $_.'DMARC Record' -eq 'Not Found' } | Set-Style -Style Critical | Out-Null) $null = ($DmarcObj | Where-Object { $_.'DMARC Record' -eq 'Found' -and $_.'Enforced' -eq 'No' } | Set-Style -Style Warning | Out-Null) $null = ($DmarcObj | Where-Object { $_.'Aggregate (rua)' -eq 'Not Set' -and $_.'DMARC Record' -eq 'Found' } | Set-Style -Style Warning | Out-Null) } }) $DmarcTableParams = @{ Name = "DMARC Records - $TenantId"; List = $false; ColumnWidths = 20, 12, 12, 8, 22, 18, 8 } if ($Report.ShowTableCaptions) { $DmarcTableParams['Caption'] = "- $($DmarcTableParams.Name)" } if ($DmarcObj.Count -gt 0) { $DmarcObj | Table @DmarcTableParams $script:ExcelSheets['DMARC'] = $DmarcObj } else { Paragraph "No authoritative domains found to check DMARC records against." } } } catch { Write-ExoError 'DMARC' "Unable to retrieve DMARC records: $($_.Exception.Message)" Paragraph "Unable to check DMARC DNS records: $($_.Exception.Message)" } #endregion #region ACSC E8 - DKIM BlankLine Paragraph "ACSC Essential Eight Assessment" BlankLine $e8VarsDkim = @{ DkimTotalDomainCount = $DkimTotalDomainCount DkimEnabledDomainCount = $DkimEnabledDomainCount Dkim2048BitCount = $Dkim2048BitCount } $e8ChecksDkim = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoE8Checks 'DKIM') -Framework 'E8' -CallerVariables $e8VarsDkim New-AbrExoE8AssessmentTable -Checks $e8ChecksDkim -Name 'DKIM' -TenantId $TenantId if ($e8ChecksDkim) { foreach ($row in $e8ChecksDkim) { $null = $script:E8AllChecks.Add([pscustomobject]@{ Section = 'DKIM' ML = $row.ML Control = $row.Control Status = $row.Status Detail = $row.Detail }) } } #endregion #region ACSC E8 - DMARC BlankLine Paragraph "ACSC Essential Eight Assessment" BlankLine $e8VarsDmarc = @{ DmarcDomainsTotal = $DmarcDomainsTotal DmarcDomainsConfigured = $DmarcDomainsConfigured DmarcDomainsCoveredPct = $DmarcDomainsCoveredPct DmarcEnforcedDomainCount = $DmarcEnforcedDomainCount DmarcRuaConfiguredCount = $DmarcRuaConfiguredCount } $e8ChecksDmarc = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoE8Checks 'DMARC') -Framework 'E8' -CallerVariables $e8VarsDmarc New-AbrExoE8AssessmentTable -Checks $e8ChecksDmarc -Name 'DMARC' -TenantId $TenantId if ($e8ChecksDmarc) { foreach ($row in $e8ChecksDmarc) { $null = $script:E8AllChecks.Add([pscustomobject]@{ Section = 'DMARC' ML = $row.ML Control = $row.Control Status = $row.Status Detail = $row.Detail }) } } #endregion #region CIS - DKIM BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment" BlankLine $cisVarsDkim = @{ DkimTotalDomainCount = $DkimTotalDomainCount DkimEnabledDomainCount = $DkimEnabledDomainCount } $cisChecksDkim = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoCISChecks 'DKIM') -Framework 'CIS' -CallerVariables $cisVarsDkim New-AbrExoCISAssessmentTable -Checks $cisChecksDkim -Name 'DKIM' -TenantId $TenantId if ($cisChecksDkim) { foreach ($row in $cisChecksDkim) { $null = $script:CISAllChecks.Add([pscustomobject]@{ Section = 'DKIM' CISControl = $row.CISControl Level = $row.Level Status = $row.Status Detail = $row.Detail }) } } #endregion #region CIS - DMARC BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment" BlankLine $cisVarsDmarc = @{ DmarcDomainsTotal = $DmarcDomainsTotal DmarcDomainsConfigured = $DmarcDomainsConfigured DmarcDomainsCoveredPct = $DmarcDomainsCoveredPct DmarcEnforcedDomainCount = $DmarcEnforcedDomainCount } $cisChecksDmarc = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoCISChecks 'DMARC') -Framework 'CIS' -CallerVariables $cisVarsDmarc New-AbrExoCISAssessmentTable -Checks $cisChecksDmarc -Name 'DMARC' -TenantId $TenantId if ($cisChecksDmarc) { foreach ($row in $cisChecksDmarc) { $null = $script:CISAllChecks.Add([pscustomobject]@{ Section = 'DMARC' CISControl = $row.CISControl Level = $row.Level Status = $row.Status Detail = $row.Detail }) } } #endregion } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'DKIM_DMARC' } } |