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 %}