github.com/StackExchange/dnscontrol/v4@v4.11.0/documentation/language-reference/domain-modifiers/SPF_BUILDER.md (about) 1 --- 2 name: SPF_BUILDER 3 parameters: 4 - label 5 - overflow 6 - overhead1 7 - raw 8 - ttl 9 - txtMaxSize 10 - parts 11 - flatten 12 parameters_object: true 13 parameter_types: 14 label: string? 15 overflow: string? 16 overhead1: string? 17 raw: string? 18 ttl: Duration? 19 txtMaxSize: number? 20 parts: string[] 21 flatten: string[]? 22 --- 23 24 DNSControl can optimize the SPF settings on a domain by flattening 25 (inlining) includes and removing duplicates. DNSControl also makes 26 it easier to document your SPF configuration. 27 28 {% hint style="warning" %} 29 **WARNING**: Flattening SPF includes is risky. Only flatten an SPF 30 setting if it is absolutely needed to bring the number of "lookups" 31 to be less than 10. In fact, it is debatable whether or not ISPs 32 enforce the "10 lookup rule". 33 {% endhint %} 34 35 36 ## The old way 37 38 Here is an example of how SPF settings are normally done: 39 40 {% code title="dnsconfig.js" %} 41 ```javascript 42 D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 43 TXT("v=spf1 ip4:198.252.206.0/24 ip4:192.111.0.0/24 include:_spf.google.com include:mailgun.org include:spf-basic.fogcreek.com include:mail.zendesk.com include:servers.mcsv.net include:sendgrid.net include:450622.spf05.hubspotemail.net ~all"), 44 END) 45 ``` 46 {% endcode %} 47 48 This has a few problems: 49 50 * No comments. It is difficult to add a comment. In particular, we want to be able to list which ticket requested each item in the SPF setting so that history is retained. 51 * Ugly diffs. If you add an element to the SPF setting, the diff will show the entire line changed, which is difficult to read. 52 * Too many lookups. The SPF RFC says that SPF settings should not require more than 10 DNS lookups. If we manually flatten (i.e. "inline") an include, we have to remember to check back to see if the settings have changed. Humans are not good at that kind of thing. 53 54 ## The DNSControl way 55 56 {% code title="dnsconfig.js" %} 57 ```javascript 58 D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 59 A("@", "10.2.2.2"), 60 MX("@", "example.com."), 61 SPF_BUILDER({ 62 label: "@", 63 overflow: "_spf%d", 64 raw: "_rawspf", 65 ttl: "5m", 66 parts: [ 67 "v=spf1", 68 "ip4:198.252.206.0/24", // ny-mail* 69 "ip4:192.111.0.0/24", // co-mail* 70 "include:_spf.google.com", // GSuite 71 "include:mailgun.org", // Greenhouse.io 72 "include:spf-basic.fogcreek.com", // Fogbugz 73 "include:mail.zendesk.com", // Zenddesk 74 "include:servers.mcsv.net", // MailChimp 75 "include:sendgrid.net", // SendGrid 76 "include:450622.spf05.hubspotemail.net", // Hubspot (Ticket# SREREQ-107) 77 "~all" 78 ], 79 flatten: [ 80 "spf-basic.fogcreek.com", // Rationale: Being deprecated. Low risk if it breaks. 81 "450622.spf05.hubspotemail.net" // Rationale: Unlikely to change without warning. 82 ] 83 }), 84 END); 85 ``` 86 {% endcode %} 87 88 By using the `SPF_BUILDER()` we gain many benefits: 89 90 * Comments can appear next to the element they refer to. 91 * Diffs will be shorter and more specific; therefore easier to read. 92 * Automatic flattening. We can specify which includes should be flattened and DNSControl will do the work. It will even warn us if the includes change. 93 94 ## Syntax 95 96 When you want to specify SPF settings for a domain, use the 97 `SPF_BUILDER()` function. 98 99 {% code title="dnsconfig.js" %} 100 ```javascript 101 D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 102 ... 103 ... 104 ... 105 SPF_BUILDER({ 106 label: "@", 107 overflow: "_spf%d", // Delete this line if you don't want big strings split. 108 overhead1: "20", // There are 20 bytes of other TXT records on this domain. Compensate for this. 109 raw: "_rawspf", // Delete this line if the default is sufficient. 110 parts: [ 111 "v=spf1", 112 // fill in your SPF items here 113 "~all" 114 ], 115 flatten: [ 116 // fill in any domains to inline. 117 ] 118 }), 119 ... 120 ... 121 END); 122 ``` 123 {% endcode %} 124 125 The parameters are: 126 127 * `label:` The label of the first TXT record. (Optional. Default: `"@"`) 128 * `overflow:` If set, SPF strings longer than 255 chars will be split into multiple TXT records. The value of this setting determines the template for what the additional labels will be named. If not set, no splitting will occur and DNSControl may generate TXT strings that are too long. 129 * `overhead1:` "Overhead for the 1st TXT record". When calculating the max length of each TXT record, reduce the maximum for the first TXT record in the chain by this amount. 130 * `raw:` The label of the unaltered SPF settings. Setting to an empty string `''` will disable this. (Optional. Default: `"_rawspf"`) 131 * `ttl:` This allows setting a specific TTL on this SPF record. (Optional. Default: using default record TTL) 132 * `txtMaxSize` The maximum size for each TXT record. Values over 255 will result in [multiple strings][multi-string]. General recommendation is to [not go higher than 450][record-size] so that DNS responses will still fit in a UDP packet. (Optional. Default: `"255"`) 133 * `parts:` The individual parts of the SPF settings. 134 * `flatten:` Which includes should be inlined. For safety purposes the flattening is done on an opt-in basis. If `"*"` is listed, all includes will be flattened... this might create more problems than is solves due to length limitations. 135 136 [multi-string]: https://tools.ietf.org/html/rfc4408#section-3.1.3 137 [record-size]: https://tools.ietf.org/html/rfc4408#section-3.1.4 138 139 `SPF_BUILDER()` returns multiple `TXT()` records: 140 141 * `TXT("@", "v=spf1 .... ~all")` 142 * This is the optimized configuration. 143 * `TXT("_spf1", "...")` 144 * If the optimizer needs to split a long string across multiple TXT records, the additional TXT records will have labels `_spf1`, `_spf2`, `_spf3`, etc. 145 * `TXT("_rawspf", "v=spf1 .... ~all")` 146 * This is the unaltered SPF configuration. This is purely for debugging purposes and is not used by any email or anti-spam system. It is only generated if flattening is requested. 147 148 149 We recommend first using this without any flattening. Make sure 150 `dnscontrol preview` works as expected. Once that is done, add the 151 flattening required to reduce the number of lookups to 10 or less. 152 153 To count the number of lookups, you can use our interactive SPF 154 debugger at [https://stackexchange.github.io/dnscontrol/flattener/index.html](https://stackexchange.github.io/dnscontrol/flattener/index.html) 155 156 # The first in a chain is special 157 158 When generating the chain of SPF 159 records, each one is max length 255. For the first item in 160 the chain, the max is 255 - "overhead1". Setting this to 255 or 161 higher has undefined behavior. 162 163 Why is this useful? 164 165 Some sites desire having all DNS queries fit in a single packet so 166 that UDP, not TCP, can be used to satisfy all requests. That means all 167 responses have to be relatively small. 168 169 When an SPF system does a "TXT" lookup, it gets SPF and non-SPF 170 records. This makes the first link in the chain extra large. 171 172 The bottom line is that if you want the TXT records to fit in a UDP 173 packet, keep increasing the value of `overhead1` until the packet 174 is no longer truncated. 175 176 Example: 177 178 ```shell 179 dig +short whatexit.org txt | wc -c 180 118 181 ``` 182 183 Setting `overhead1` to 118 should be sufficient. 184 185 ```shell 186 dig +short stackoverflow.com txt | wc -c 187 582 188 ``` 189 190 Since 582 is bigger than 255, it might not be possible to achieve the 191 goal. Any value larger than 255 will disable all flattening. Try 192 170, then 180, 190 until you get the desired results. 193 194 A validator such as 195 [https://www.kitterman.com/spf/validate.html](https://www.kitterman.com/spf/validate.html) 196 will tell you if the queries are being truncated and TCP was required 197 to get the entire record. (Sadly it caches heavily.) 198 199 ## Notes about the `spfcache.json` 200 201 DNSControl keeps a cache of the DNS lookups performed during 202 optimization. The cache is maintained so that the optimizer does 203 not produce different results depending on the ups and downs of 204 other people's DNS servers. This makes it possible to do `dnscontrol 205 push` even if your or third-party DNS servers are down. 206 207 The DNS cache is kept in a file called `spfcache.json`. If it needs 208 to be updated, the proper data will be written to a file called 209 `spfcache.updated.json` and instructions such as the ones below 210 will be output telling you exactly what to do: 211 212 ```shell 213 dnscontrol preview 214 1 Validation errors: 215 WARNING: 2 spf record lookups are out of date with cache (_spf.google.com,_netblocks3.google.com). 216 Wrote changes to spfcache.updated.json. Please rename and commit: 217 $ mv spfcache.updated.json spfcache.json 218 $ git commit spfcache.json 219 ``` 220 221 In this case, you are being asked to replace `spfcache.json` with 222 the newly generated data in `spfcache.updated.json`. 223 224 Needing to do this kind of update is considered a validation error 225 and will block `dnscontrol push` from running. 226 227 Note: The instructions are hardcoded strings. The filenames will 228 not change. 229 230 Note: The instructions assume you use git. If you use something 231 else, please do the appropriate equivalent command. 232 233 ## Caveats 234 235 1. DNSControl 'gives up' if it sees SPF records it can't understand. 236 This includes: syntax errors, features that our spflib doesn't know 237 about, overly complex SPF settings, and anything else that we we 238 didn't feel like implementing. 239 240 2. The TXT record that is generated may exceed DNS limits. dnscontrol 241 will not generate a single TXT record that exceeds DNS limits, but 242 it ignores the fact that there may be other TXT records on the same 243 label. For example, suppose it generates a TXT record on the bare 244 domain (stackoverflow.com) that is 250 bytes long. That's fine and 245 doesn't require a continuation record. However if there is another 246 TXT record (not an SPF record, perhaps a TXT record used to verify 247 domain ownership), the total packet size of all the TXT records 248 could exceed 512 bytes, and will require EDNS or a TCP request. 249 250 3. DNSControl does not warn if the number of lookups exceeds 10. 251 We hope to implement this some day. 252 253 4. The `redirect=` directive is only partially implemented. We only 254 handle the case where redirect is the last item in the SPF record. 255 In which case, it is equivalent to `include:`. 256 257 258 ## Advanced Technique: Interactive SPF Debugger 259 260 DNSControl includes an experimental system for viewing 261 SPF settings: 262 263 [https://stackexchange.github.io/dnscontrol/flattener/index.html](https://stackexchange.github.io/dnscontrol/flattener/index.html) 264 265 You can also run this locally (it is self-contained) by opening 266 `dnscontrol/docs/flattener/index.html` in your browser. 267 268 You can use this to determine the minimal number of domains you 269 need to flatten to have fewer than 10 lookups. 270 271 The output is as follows: 272 273 1. The top part lists the domain as it current is configured, how 274 many lookups it requires, and includes a checkbox for each item 275 that could be flattened. 276 277 2. Fully flattened: This section shows the SPF configuration if you 278 fully flatten it. i.e. This is what it would look like if all the 279 checkboxes were checked. Note that this result is likely to be 280 longer than 255 bytes, the limit for a single TXT string. 281 282 3. Fully flattened split: This takes the "fully flattened" result 283 and splits it into multiple DNS records. To continue to the next 284 record an include is added. 285 286 287 ## Advanced Technique: Define once, use many 288 289 In some situations we define an SPF setting once and want to re-use 290 it on many domains. Here's how to do this: 291 292 {% code title="dnsconfig.js" %} 293 ```javascript 294 var SPF_MYSETTINGS = SPF_BUILDER({ 295 label: "@", 296 overflow: "_spf%d", 297 raw: "_rawspf", 298 parts: [ 299 "v=spf1", 300 ... 301 "~all" 302 ], 303 flatten: [ 304 ... 305 ] 306 }); 307 308 D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 309 SPF_MYSETTINGS, 310 END); 311 312 D("example2.tld", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 313 SPF_MYSETTINGS, 314 END); 315 ``` 316 {% endcode %}