|
| 1 | +function Invoke-CIPPStandardAddDMARCToMOERA { |
| 2 | + <# |
| 3 | + .FUNCTIONALITY |
| 4 | + Internal |
| 5 | + .COMPONENT |
| 6 | + (APIName) AddDMARCToMOERA |
| 7 | + .SYNOPSIS |
| 8 | + (Label) Enables DMARC on MOERA (onmicrosoft.com) domains |
| 9 | + .DESCRIPTION |
| 10 | + (Helptext) Note: requires 'Domain Name Administrator' GDAP role. This should be enabled even if the MOERA (onmicrosoft.com) domains is not used for sending. Enabling this prevents email spoofing. The default value is 'v=DMARC1; p=reject;' recommended because the domain is only used within M365 and reporting is not needed. Omitting pct tag default to 100% |
| 11 | + (DocsDescription) Note: requires 'Domain Name Administrator' GDAP role. Adds a DMARC record to MOERA (onmicrosoft.com) domains. This should be enabled even if the MOERA (onmicrosoft.com) domains is not used for sending. Enabling this prevents email spoofing. The default record is 'v=DMARC1; p=reject;' recommended because the domain is only used within M365 and reporting is not needed. Omitting pct tag default to 100% |
| 12 | + .NOTES |
| 13 | + CAT |
| 14 | + Global Standards |
| 15 | + TAG |
| 16 | + "CIS" |
| 17 | + "Security" |
| 18 | + "PhishingProtection" |
| 19 | + ADDEDCOMPONENT |
| 20 | + {"type":"autoComplete","multiple":false,"creatable":true,"required":false,"placeholder":"v=DMARC1; p=reject; (recommended)","label":"Value","name":"standards.AddDMARCToMOERA.RecordValue","options":[{"label":"v=DMARC1; p=reject; (recommended)","value":"v=DMARC1; p=reject;"}]} |
| 21 | + IMPACT |
| 22 | + Low Impact |
| 23 | + ADDEDDATE |
| 24 | + 2025-06-16 |
| 25 | + POWERSHELLEQUIVALENT |
| 26 | + Portal only |
| 27 | + RECOMMENDEDBY |
| 28 | + "CIS" |
| 29 | + "Microsoft" |
| 30 | + UPDATECOMMENTBLOCK |
| 31 | + Run the Tools\Update-StandardsComments.ps1 script to update this comment block |
| 32 | + .LINK |
| 33 | + https://docs.cipp.app/user-documentation/tenant/standards/list-standards |
| 34 | + #> |
| 35 | + |
| 36 | + param($Tenant, $Settings) |
| 37 | + #$Rerun -Type Standard -Tenant $Tenant -API 'AddDMARCToMOERA' -Settings $Settings |
| 38 | + |
| 39 | + $RecordModel = [PSCustomObject]@{ |
| 40 | + HostName = '_dmarc' |
| 41 | + TtlValue = 3600 |
| 42 | + Type = 'TXT' |
| 43 | + Value = $Settings.RecordValue.Value ?? "v=DMARC1; p=reject;" |
| 44 | + } |
| 45 | + |
| 46 | + # Get all fallback domains (onmicrosoft.com domains) and check if the DMARC record is set correctly |
| 47 | + try { |
| 48 | + $Domains = New-GraphGetRequest -scope 'https://admin.microsoft.com/.default' -TenantID $Tenant -Uri 'https://admin.microsoft.com/admin/api/Domains/List' | Where-Object -Property Name -like "*.onmicrosoft.com" |
| 49 | + |
| 50 | + $CurrentInfo = $Domains | ForEach-Object { |
| 51 | + # Get current DNS records that matches _dmarc hostname and TXT type |
| 52 | + $CurrentRecords = New-GraphGetRequest -scope 'https://admin.microsoft.com/.default' -TenantID $Tenant -Uri "https://admin.microsoft.com/admin/api/Domains/Records?domainName=$($_.Name)" | Select-Object -ExpandProperty DnsRecords | Where-Object { $_.HostName -eq $RecordModel.HostName -and $_.Type -eq $RecordModel.Type } |
| 53 | + |
| 54 | + if ($CurrentRecords.count -eq 0) { |
| 55 | + #record not found, return a model with Match set to false |
| 56 | + [PSCustomObject]@{ |
| 57 | + DomainName = $_.Name |
| 58 | + Match = $false |
| 59 | + CurrentRecord = $null |
| 60 | + } |
| 61 | + } else { |
| 62 | + foreach ($CurrentRecord in $CurrentRecords) { |
| 63 | + # Create variable matching the RecordModel used for comparison |
| 64 | + $CurrentRecordModel = [PSCustomObject]@{ |
| 65 | + HostName = $CurrentRecord.HostName |
| 66 | + TtlValue = $CurrentRecord.TtlValue |
| 67 | + Type = $CurrentRecord.Type |
| 68 | + Value = $CurrentRecord.Value |
| 69 | + } |
| 70 | + |
| 71 | + # Compare the current record with the expected record model |
| 72 | + if (!(Compare-Object -ReferenceObject $RecordModel -DifferenceObject $CurrentRecordModel -Property HostName, TtlValue, Type, Value)) { |
| 73 | + [PSCustomObject]@{ |
| 74 | + DomainName = $_.Name |
| 75 | + Match = $true |
| 76 | + CurrentRecord = $CurrentRecord |
| 77 | + } |
| 78 | + } else { |
| 79 | + [PSCustomObject]@{ |
| 80 | + DomainName = $_.Name |
| 81 | + Match = $false |
| 82 | + CurrentRecord = $CurrentRecord |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + # Check if match is true and there is only one DMARC record for the domain |
| 89 | + $StateIsCorrect = $false -notin $CurrentInfo.Match -and $CurrentInfo.Count -eq 1 |
| 90 | + } catch { |
| 91 | + if ($_.Exception.Message -like '*403*') { |
| 92 | + $Message = "AddDMARCToMOERA: Insufficient permissions. Please ensure the tenant GDAP relationship includes the 'Domain Name Administrator' role: $(Get-NormalizedError -message $_.Exception.message)" |
| 93 | + } |
| 94 | + else { |
| 95 | + $Message = "Failed to get dns records for MOERA domains: $(Get-NormalizedError -message $_.Exception.message)" |
| 96 | + } |
| 97 | + Write-LogMessage -API 'Standards' -tenant $tenant -message $Message -sev Error |
| 98 | + throw $Message |
| 99 | + } |
| 100 | + |
| 101 | + If ($Settings.remediate -eq $true) { |
| 102 | + if ($StateIsCorrect -eq $true) { |
| 103 | + Write-LogMessage -API 'Standards' -tenant $tenant -message 'DMARC record is already set for all MOERA (onmicrosoft.com) domains.' -sev Info |
| 104 | + } |
| 105 | + else { |
| 106 | + # Loop through each domain and set the DMARC record, existing misconfigured records and duplicates will be deleted |
| 107 | + foreach ($Domain in ($CurrentInfo | Sort-Object -Property DomainName -Unique)) { |
| 108 | + try { |
| 109 | + foreach ($Record in ($CurrentInfo | Where-Object -Property DomainName -eq $Domain.DomainName)) { |
| 110 | + if ($Record.CurrentRecord) { |
| 111 | + New-GraphPOSTRequest -tenantid $tenant -scope 'https://admin.microsoft.com/.default' -Uri "https://admin.microsoft.com/admin/api/Domains/Record?domainName=$($Domain.DomainName)" -Body ($Record.CurrentRecord | ConvertTo-Json -Compress) -AddedHeaders @{'x-http-method-override' = 'Delete'} |
| 112 | + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted incorrect DMARC record for domain $($Domain.DomainName)" -sev Info |
| 113 | + } |
| 114 | + New-GraphPOSTRequest -tenantid $tenant -scope 'https://admin.microsoft.com/.default' -type "PUT" -Uri "https://admin.microsoft.com/admin/api/Domains/Record?domainName=$($Domain.DomainName)" -Body (@{RecordModel = $RecordModel} | ConvertTo-Json -Compress) |
| 115 | + Write-LogMessage -API 'Standards' -tenant $tenant -message "Set DMARC record for domain $($Domain.DomainName)" -sev Info |
| 116 | + } |
| 117 | + } |
| 118 | + catch { |
| 119 | + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set DMARC record for domain $($Domain.DomainName): $(Get-NormalizedError -message $_.Exception.message)" -sev Error |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + if ($Settings.alert -eq $true) { |
| 126 | + if ($StateIsCorrect -eq $true) { |
| 127 | + Write-LogMessage -API 'Standards' -tenant $tenant -message 'DMARC record is already set for all MOERA (onmicrosoft.com) domains.' -sev Info |
| 128 | + } else { |
| 129 | + $UniqueDomains = ($CurrentInfo | Sort-Object -Property DomainName -Unique) |
| 130 | + $NotSetDomains = @($UniqueDomains | ForEach-Object {if ($_.Match -eq $false -or ($CurrentInfo | Where-Object -Property DomainName -eq $_.DomainName).Count -eq 1) { $_.DomainName } }) |
| 131 | + $Message = "DMARC record is not set for $($NotSetDomains.count) of $($UniqueDomains.count) MOERA (onmicrosoft.com) domains." |
| 132 | + |
| 133 | + Write-StandardsAlert -message $Message -object @{MissingDMARC = ($NotSetDomains -join ', ')} -tenant $tenant -standardName 'AddDMARCToMOERA' -standardId $Settings.standardId |
| 134 | + Write-LogMessage -API 'Standards' -tenant $tenant -message "$Message. Missing for: $($NotSetDomains -join ', ')" -sev Info |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + if ($Settings.report -eq $true) { |
| 139 | + set-CIPPStandardsCompareField -FieldName 'standards.AddDMARCToMOERA' -FieldValue $StateIsCorrect -TenantFilter $Tenant |
| 140 | + Add-CIPPBPAField -FieldName 'AddDMARCToMOERA' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant |
| 141 | + } |
| 142 | +} |
0 commit comments