Die immer mehr verbreite Nutzung von SaaS E-Mail Dienstleistern ala Exchange Online, G-Suite, Amazon SES, SendGrid und anderer ist eine Herausforderung für E-Mail Administratoren. Um zu verhindern das E-Mails an die Kunden im Spam Filter landen oder gar nicht angenommen werden, ist eine einwandfreie Konfiguration von DMARC, DKIM und SPF Pflicht.
Gerade bei SPF ist man schnell dazu geneigt einfach alle include
Einträge der Drittanbieter einzupflegen. Das kann aber auch ins Auge gehen, wenn z.B. die maximale Anzahl an DNS Lookups (10) oder die maximale Länge eines TXT DNS Records (255) überschritten wird. Gerade bei include
sind die Auswirkungen nicht immer direkt sichtbar, weil viele Dienste selbst ebenfalls include
nutzen um die IP Ranges zu organisieren.
Der Überblick über die eingebundenen Dienste kann da schnell verloren gehen.
Um die vorhandenen SPF DNS Records, inklusive aller per include eingetragenen Diensten abzufragen, habe ich die Funktion Resolve-SPFRecord
geschrieben.
Funktionsübersicht
Unterstützte SPF Direktiven und Funktionen
- include
- mx
- a
- ip4 und ip6
- redirect
- Warnung bei zu vielen
include
Einträgen
Nicht unterstützt werden
Nutzung
Für die Abfrage der entsprechenden TXT Records im DNS wird nur der Paramater Name
benötigt. Hier muss die abzufragende Domäne angegeben werden, das Skript macht den Rest.
Resolve-SPFRecord -Name domainname.tld
Es empfiehlt sich, für bessere Lesbarkeit, das Ergebnis mit Format-Table
ausgeben zu lassen.
Resolve-SPFRecord -Name domainname.tld | ft
Alternativer DNS Server
Optional kann noch der Paramater Server
genutzt werden. Hiermit ändert man den abzufragenden DNS Server. Das kann z.B. hilfreich sein, wenn man die DNS Änderungen kurz nach der Veröffentlichung direkt am eigenen Root Nameserver testen möchte, oder es z.B. Einschränkungen gibt welchen DNS Server der eigene Client abfragen darf.
Download
Das komplette Skript steht hier zum Download bereit oder kann auch aus dem Code Block kopiert werden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
function Resolve-SPFRecord {
[CmdletBinding()]
param (
# Domain Name
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 1)]
[string]$Name,
# DNS Server to use
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
Position = 2)]
[string]$Server = "1.1.1.1",
# If called nested provide a referrer to build valid objects
[Parameter(Mandatory = $false)]
[string]$Referrer
)
begin {
class SPFRecord {
[string] $SPFSourceDomain
[string] $IPAddress
[string] $Referrer
[string] $Qualifier
[bool] $Include
# Constructor: Creates a new SPFRecord object, with a specified IPAddress
SPFRecord ([string] $IPAddress) {
$this.IPAddress = $IPAddress
}
# Constructor: Creates a new SPFRecord object, with a specified IPAddress and DNSName
SPFRecord ([string] $IPAddress, [String] $DNSName) {
$this.IPAddress = $IPAddress
$this.SPFSourceDomain = $DNSName
}
# Constructor: Creates a new SPFRecord object, with a specified IPAddress and DNSName and
SPFRecord ([string] $IPAddress, [String] $DNSName, [String] $Qualifier) {
$this.IPAddress = $IPAddress
$this.SPFSourceDomain = $DNSName
$this.Qualifier = $Qualifier
}
}
}
process {
# Keep track of number of DNS queries
# DNS Lookup Limit = 10
# https://tools.ietf.org/html/rfc7208#section-4.6.4
# Query DNS Record
$DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type TXT
# Check SPF record
$SPFRecord = $DNSRecords | Where-Object { $_.Strings -match "^v=spf1" }
# Validate SPF record
$SPFCount = ($SPFRecord | Measure-Object).Count
if ( $SPFCount -eq 0) {
# If there is no error show an error
Write-Error "No SPF record found for `"$Name`""
}
elseif ( $SPFCount -ge 2 ) {
# Multiple DNS Records are not allowed
# https://tools.ietf.org/html/rfc7208#section-3.2
Write-Error "There is more than one SPF for domain `"$Name`""
}
else {
# Multiple Strings in a Single DNS Record
# https://tools.ietf.org/html/rfc7208#section-3.3
$SPFString = $SPFRecord.Strings -join ''
# Split the directives at the whitespace
$SPFDirectives = $SPFString -split " "
# Check for a redirect
if ( $SPFDirectives -match "redirect" ) {
$RedirectRecord = $SPFDirectives -match "redirect" -replace "redirect="
Write-Verbose "[REDIRECT]`t$RedirectRecord"
# Follow the include and resolve the include
Resolve-SPFRecord -Name "$RedirectRecord" -Server $Server -Referrer $Name
}
else {
# Extract the qualifier
$Qualifier = switch ( $SPFDirectives -match "^[+-?~]all$" -replace "all" ) {
"+" { "pass" }
"-" { "fail" }
"~" { "softfail" }
"?" { "neutral" }
}
$ReturnValues = foreach ($SPFDirective in $SPFDirectives) {
switch -Regex ($SPFDirective) {
"%[{%-_]" {
Write-Warning "[$_]`tMacros are not supported. For more information, see https://tools.ietf.org/html/rfc7208#section-7"
Continue
}
"^exp:.*$" {
Write-Warning "[$_]`tExplanation is not supported. For more information, see https://tools.ietf.org/html/rfc7208#section-6.2"
Continue
}
'^include:.*$' {
# Follow the include and resolve the include
Resolve-SPFRecord -Name ( $SPFDirective -replace "^include:" ) -Server $Server -Referrer $Name
}
'^ip[46]:.*$' {
Write-Verbose "[IP]`tSPF entry: $SPFDirective"
$SPFObject = [SPFRecord]::New( ($SPFDirective -replace "^ip[46]:"), $Name, $Qualifier)
if ( $PSBoundParameters.ContainsKey('Referrer') ) {
$SPFObject.Referrer = $Referrer
$SPFObject.Include = $true
}
$SPFObject
}
'^a:.*$' {
Write-Verbose "[A]`tSPF entry: $SPFDirective"
$DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type A
# Check SPF record
foreach ($IPAddress in ($DNSRecords.IPAddress) ) {
$SPFObject = [SPFRecord]::New( $IPAddress, ($SPFDirective -replace "^a:"), $Qualifier)
if ( $PSBoundParameters.ContainsKey('Referrer') ) {
$SPFObject.Referrer = $Referrer
$SPFObject.Include = $true
}
$SPFObject
}
}
'^mx:.*$' {
Write-Verbose "[MX]`tSPF entry: $SPFDirective"
$DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type MX
foreach ($MXRecords in ($DNSRecords.NameExchange) ) {
# Check SPF record
$DNSRecords = Resolve-DnsName -Server $Server -Name $MXRecords -Type A
foreach ($IPAddress in ($DNSRecords.IPAddress) ) {
$SPFObject = [SPFRecord]::New( $IPAddress, ($SPFDirective -replace "^mx:"), $Qualifier)
if ( $PSBoundParameters.ContainsKey('Referrer') ) {
$SPFObject.Referrer = $Referrer
$SPFObject.Include = $true
}
$SPFObject
}
}
}
Default {
Write-Warning "[$_]`t Unknown directive"
}
}
}
$DNSQuerySum = $ReturnValues | Select-Object -Unique SPFSourceDomain | Measure-Object | Select-Object -ExpandProperty Count
if ( $DNSQuerySum -gt 6) {
Write-Warning "Watch your includes!`nThe maximum number of DNS queries is 10 and you have already $DNSQuerySum.`nCheck https://tools.ietf.org/html/rfc7208#section-4.6.4"
}
if ( $DNSQuerySum -gt 10) {
Write-Error "Too many DNS queries made ($DNSQuerySum).`nMust not exceed 10 DNS queries.`nCheck https://tools.ietf.org/html/rfc7208#section-4.6.4"
}
$ReturnValues
}
}
}
end {
}
}
|