Customer subdomain authentication

EDIT: Now with a production-ready implementation I talk about more here.

On Tuesday I wrote about using DNS wildcards to implement customer-specific subdomains for email authentication.

As I said then, that approach isn’t perfect. You’d much prefer to have per-customer domain authentication, where each customer has their own DKIM d= and ideally their own SPF records, rather than having all customers sharing those records and relying on loose DMARC alignment to have them to work with a per-customer subdomain in the 5322 From: header.

But doing that with DNS wildcards would have some odd side effects, such as TXT records appearing where they weren’t expected, in ways that could trigger bugs in rarely tested code paths at mailbox providers and potentially even open up security problems.

I mentioned using a “stunt” DNS server would be one option to do that, and then quite a few people asked me what I meant by that. A stunt DNS server is one that doesn’t necessarily behave like a normal DNS server, rather it has special behaviour to provide a particular service. Geo load balancers that return different answers depending on the user asking a DNS question, so as to point them at a server that’s close to them would be one common example.

For customer-specific subdomains at scale I want a DNS server that will accept queries for customer-specific authentication records and synthesize appropriate DNS replies for those, without you needing to generate static DNS records for your hundreds of thousands of small customers.

What would our ideal for authenticating small customers for our ESP, SausageMail, look like?

We’d want to use a customer-specific subdomain in the 5322 From: header. We’d want per-customer authentication, which would mean a customer-specific return path, with a matching SPF text records, and customer specific DKIM public keys to allow DKIM signing with a customers subdomain in the d= field. And we’d like to do this in a way that doesn’t require us to modify thousands of records any time we want to add a new IP to our pool of MTAs or we want to rotate our DKIM keys.

So, for our customer “snufkin”, we’d want to have:

  • From: “Snufkin the Cat, Esq.” <snufkin@snufkin.sausagemail.com>
  • Return-Path: bounces@bounces.snufkin.sausagemail.com
  • DKIM-Signature: … s=k1; d=snufkin.sausagemail.com

The message generation and DKIM signing for that is probably simple to do – it’s just inserting a customer name in some templated headers. But what DNS would we need?

; For replies to the email address in the From:
snufkin.sausagemail.com         MX 10 replies.sausagemail.com

; For the return path, bounce handling and SPF
bounces.snufkin.sausagemail.com MX 10 bounces.sausagemail.com
bounces.snufkin.sausagemail.com TXT "v=spf1 include:_spf.sausagemail.com"

; DKIM public keys
k1._domainkey.snufkin.sausagemail.com CNAME k1._dkim.sausagemail.com
k2._domainkey.snufkin.sausagemail.com CNAME k2._dkim.sausagemail.com

; And DMARC
_dmarc.snufkin.sausagemail.com TXT "v=DMARC1 p=none rua=..."

This gives us unique authentication domains for each customer, DMARC-aligned with the from address and shouldn’t require too much maintenance. Now we just need to stamp those records out a few hundred thousand times.

When I’m building stunt DNS servers (we have several as part of our onboarding infrastructure for clients, so that we can easily route their sample email streams into our databases, and monitor everything about their mail delivery, including DNS traffic) I usually write a dedicated app that serves DNS directly. But many off the shelf DNS servers support scripting and plugins to do the same sort of thing, so I wrote a simple backend for PowerDNS that’ll generate templated DNS responses in the way we need.

That backend is regexdns – boringly named because it serves DNS based on regular expressions. (You can grab the source and binaries from github to play with if you’d find it interesting / useful.)

It’s configured with something that looks sort of like a regular DNS zone file, but with some added patterns to match things in the request and return them in the response.

sausagemail.com 3600 IN SOA ns1.sausagemail.com steve.blighty.com. 2023102014 10800 3600 2592000 600

; Our actual bounce handler and reply handler
bounces.sausagemail.com 3600 IN A 10.11.12.13
replies.sausagemail.com 3600 IN A 10.11.12.14

; Our DKIM public key
k1._dkim.sausagemail.com    3600 IN TXT "v=DKIM1; k=RSA; p=key-goes-here"
k2._dkim.sausagemail.com    3600 IN TXT "v=DKIM1; k=RSA; p=other-key-goes-here"

; Our SPF stuff
_spf.sausagemail.com 3600 IN TXT "v=spf1 ip4:10.11.12.10 ~all"

; Per-customer generation
; DKIM public keys
(?P<selector>[^.]+)\._domainkey\.(?P<customer>[^.]+)\.sausagemail\.com 3600 IN CNAME ${selector}._dkim.sausagemail.com

; Return path, point to bounce handler
bounces\.(?P<customer>[^.]+)\.sausagemail\.com 3600 IN MX 10 bounces.sausagemail.com

; SPF
bounces\.(?P<customer>[^.]+)\.sausagemail\.com 3600 IN TXT "v=spf1 include:_spf.sausagemail.com"

; The domain in the From: header, for replies
(?P<customer>[^.]+)\.sausagemail\.com 3600 IN MX 10 replies.sausagemail.com

; And DMARC, because why not?
_dmarc\.(?P<customer>[^.]+)\.sausagemail\.com 3600 IN TXT "v=DMARC1 p=none rua=rua+${customer}@sausagemail.com"Code language: PHP (php)

(If it’s tricky to read in the blog embed you can see the actual configuration file here.)

On the left hand side of each line the incantation (?P<customer>[^.]+) means “in an incoming DNS query there should be one component of a hostname here, let’s call it ‘customer’“, while on the right hand side ${customer} means “replace this with the customer name you just got, and send it as the DNS reply”.

This is all up and running live, at least when this post was written, so you can see what the DKIM public keys for snufkin look like:

k1._domainkey.snufkin.sausagemail.com TXT

Or his SPF records:

bounces.snufkin.sausagemail.com TXT

Or the DMARC records for toffle:

_dmarc.toffle.sausagemail.com TXT

This is a fairly general purpose approach any time you need to stamp out a lot of similar DNS records.

Related Posts

Are they using DKIM?

It’s easy to tell if a domain is using SPF – look up the TXT record for the domain and see if any of them begin with “v=spf1”. If one does, they’re using SPF. If none do, they’re not. (If more than one does? They’re publishing invalid SPF.)
AOL are publishing SPF. Geocities aren’t.
For DKIM it’s harder, as a DKIM key isn’t published at a well-known place in DNS. Instead, each signed email includes a “selector” and you look up a record by combining that selector with the fixed string “._domainkey.” and the domain.
If you have DKIM-signed mail from them then you can find the selector (s=) in the DKIM-Signature header and look up the key. For example, Amazon are using a selector of “taugkdi5ljtmsua4uibbmo5mda3r2q3v”, so I can look up TXT records for “taugkdi5ljtmsua4uibbmo5mda3r2q3v._domainkey.amazon.com“, see that there’s a TXT record returned and know there’s a DKIM key.
That’s a particularly obscure selector, probably one they’re using to track DKIM lookups to the user the mail was sent to, but even if a company is using a selector like “jun2016” you’re unlikely to be able to guess it.
But there’s a detail in the DNS spec that says that if a hostname exists, meaning it’s in DNS, then all the hostnames “above” it in the DNS tree also exist (even if there are no DNS records for them). So if anything,_domainkey.example.com exists in DNS, so does _domainkey.example.com. And, conversely, if _domainkey.example.com doesn’t exist, no subdomain of it exists either.
What does it mean for a hostname to exist in DNS? That’s defined by the two most common responses you get to a DNS query.
One is “NOERROR” – it means that the hostname you asked about exists, even if there are no resource records returned for the particular record type you asked about.
The other is “NXDOMAIN” – it means that the hostname you asked about doesn’t exist, for any record type.
So if you look up _domainkey.aol.com you’ll see a “NOERROR” response, and know that AOL have published DKIM public keys and so are probably using DKIM.
(This is where Steve tries to find a domain that isn’t publishing DKIM keys … Ah! Al’s blog!)
If you look up _domainkey.spamresource.com you’ll see an “NXDOMAIN” response, so you know Al isn’t publishing any DKIM public keys, so isn’t sending any DKIM signed mail using that domain.
This isn’t 100% reliable, unfortunately. Some nameservers will (wrongly) return an NXDOMAIN even if there are subdomains, so you might sometimes get an NXDOMAIN even for a domain that is publishing DKIM. shrug
Sometimes you’ll see an actual TXT record in response – e.g. Yahoo or EBay – that’s detritus left over from the days of DomainKeys, a DomainKeys policy record, and it means nothing today.

Read More

Don't just follow the HOWTO

speakingIconForBlogThere are so many moving parts to ensure good email deliverability. Email marketers need to know marketing, they need to know email and they need to know design. The technical bits of email can be a challenge to learn, and many folks who write tutorials and How-Tos write them for a different audience than marketers.
One of the things I’m trying to do is demystify the technical end of email for marketers. Today I talked about authentication in the Only Influencers newsletter. Check it out!
Understanding the technical: Authentication
Authentication in general

Read More

Why is DMARC failing?

Multiple times over the last few weeks folks have posted a screenshot of Google Postmaster tools showing some percentage of mail failing DMARC. They then ask why DMARC is failing. Thanks to how DMARC was designed, they don’t need to ask anyone this, they have all the data they need to work this out themselves.

Read More