SPF, DKIM, and DMARC Oh My!

email SPF DKIM DMARC

Email remains a cornerstone of communication, ensuring the authenticity and integrity of messages is paramount. To combat the ever-present threats of spoofing and phishing, three technologies are a foundation for protecting email channels: Sender Policy Framework (SPF), DomainKeys Identified Mail (DKIM), and Domain-based Message Authentication, Reporting & Conformance (DMARC). Together these protocols empower domain owners to define legitimate sending sources, cryptographically sign their emails, and establish policies for handling unauthenticated messages, collectively building a more trustworthy and secure email ecosystem for senders and recipients.

I run my mail services on a small Fedora Linux server running Postfix, which is integrated with a few open source packages and a few custom tools.. It has served me very well for the last few years. I find that I have a bit more control over my email and how I manage my communications. It does require period attention and maintenance. I recently completed a small project to make sure that I had SPF, DKIM, and DMARC setup for each of my personal domains. This allows me to verify incoming email and also gives recievers the ability to authenticate email from my domains, which protects the reputation of my domains. The process is relatively simple for my platform.

Setting Up SPF

The first major step in setting up SPF is to create the SPF records in DNS. These are simple TXT records that identify all of sources where email from your domains originate. You will need to know all of the IP addresses and/or networks where your email originates.This is a bit more challenging if you use cloud services to send email on your behalf. Your service provider should be able to assist with identify their IP addresses. SPF records look very different depending on the sender sources so I won't go into all of the details. The SPF wiki page explains the syntax fairly well. All my email is generated from my single server so my SPF record looks like: v=spf1 mx a ip4:a.b.c.d/e -all where a.b.c.d/e is the IP address of my server and the -all instructs the receiver to fail the SPF check for all email not from this IP address. There are less severe options for unmatched senders, but I am confident in my email sources. You might want to consider a less restrictive option while you debug your senders list.

The procedure for publishing the TXT record will vary depending on your hosting and DNS provider. For example, in my SPF TXT record for this domain the hostname is peterwoods.online and the value is v=spf1 mx a ip4:173.255.253.43/32 -all.

After creating SPF records for each of my domains, I installed the pypolicyd-spf package and configured Postfix to require the SPF checks for incoming email. This requires small changes to the master.cf and main.cf files. Please note that the file paths in this example are specific to where the package installs the milter.

Add the following at the end of /etc/postfix/master.cf.

policyd-spf   unix  -       n       n       -       0       spawn
    user=pyspf-milter argv=/usr/libexec/postfix/policyd-spf

Add the following at the end of /etc/postfix/main.cf at the end smtp_recipient_restrictions seconf. Be sure to add the comma to the end of the previous line.

check_policy_service unix:private/policyd-spf

Restart Postfix on your server. Monitor your email logs and check SMTP headers in individual messages to make sure that everything is working. You might also want to do some outbound tests with the major cloud email providers to make sure your email is going through.

Setting Up DKIM

Setting up DKIM is a bit more complex. The process involves installing packages, identifying servers, generating signing keys, and publishing them in a DNS TXT record. I installed the opendkim and opendkim-tools packages first. All of the major configuration files are located under /etc/opendkim.

The next major step is to create key pairs for each domain and subdomain that sends email. I do not use subdomains for email so I can create just one key pair per domain and use the default selector. The general form of my command is: opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v where example.com is the domain that I am generating the key for and default is the selector. Note that the domain name is in the directory path to key location so you will need to create a directory for each domain and subdomain. I decided to 2048 for my key size.

The opendkim-genkey command will generate two files called default.txt and default.private. The private file is just that. Do not share or publish it anywhere. The txt file contains the information that you want to publish in your DNS record, but you will need to modify the format slightly. If you look at the file, you will notice that the key is split across multiple lines. The value for your TXT record will start with the v=DKIM1 and end with the string that ends right before the last parenthesis in the last line. Be sure to remove any quotation marks and spaces when joining the lines for your TXT record. Do not modify the original file. Join them in a scratch file if you need to. For example, in the DMARC TXT record for this domain, the hostname contains default._domainkey and the value of v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ_truncated_for_readability_yPfCRMrLpNnnT+LzWXXtbENwIDAQAB.

The next step is to update the KeyTable, SigningTable and TrustedHosts files. On a good note, each file has a good example line to follow.

In KeyTable, add a line similar to:

default._domainkey.example.com example.com:default:/etc/opendkim/keys/default.private

In SigningTable, add a line similar to:

*@example.com default._domainkey.example.com

Add your sending IP address and hostnames to TrustedHosts.

host.example.com
192.168.1.0/24

Edit /etc/opendkim.conf and change the value for the Mode field to sv. This enables signing and verification.

Mode  sv

Optional I also prefer to use a TCP connection instead of a socket. In the same configuration file, find the Socket line and change to the following. You might need to tinker with file permissions if you stay with the file system socket.

Socket  inet:8891@localhost

Find the following commented out values and uncomment them.

KeyTable
SigningTable
ExternalIgnoreList

Enable and start the opendkim service.

systemctl enable opendkim.service
systemctl start opendkim.service

Edit /etc/postfix/main.cf and at the following lines to add the DKIM milter. Choose the correct smtpd_milters line for either a socket or TCP connection.

smtpd_milters = unix:/run/opendkim/opendkim.sock
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept

Restart postfix with the following command.

systemctl restart postfix.service

Send some test messages through your email server. Check the maillog file for errors. If the emails make it to their destination, check the message headers to make sure that your message is signed. Just search for DKIM. You should see the Dkim-Filter and Dkim-Signature headers for outgoing email.

Setting up DMARC

Yes we need another DNS entry for DMARC. The abbreviate DNS entry is another TXT record with host set to _dmarc and the value set to v=DMARC1; p=policy; rua=mailto:reports@example.com where policy is action that postfix when it fails to authenticate an email. The choices are none means take no action, quarantine, or reject. The email address in the TXT record is where you want reports to be sent for failures.

After creating the DMARC records for each of my domains, I installed the opendmarc and opendmarc-tools packages on my server. The only change that I made /etc/opendmarc.conf was to change the socket to a TCP connection. The syntax is a bit odd with the port number in front of the IP address. It works just fine as a socket connection.

Socket inet:8893@127.0.0.1

Enable and start the opendmarc daemon.

systemctl enable opendmarc.service
systemctl start opendmarc.service

Add the milter to the Postfix configuration. Edit the /etc/postfix/main.cf file and append the appropriate socket type.

smtpd_milters = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
smtpd_milters = inet:127.0.0.1:8891, inet:127.0.0.1:8893

Restart Postfix and validate the results by sending and receiving emails.

systemctl restart postfix.service

Not every email provider is setup to verify DMARC and send reports. I did find that outlook.com did send a report back the first time that I sent email to that service.

Wrapping Up

Implementing SPF, DKIM, and DMARC might seem like a technical deep dive, and admittedly, it requires a bit of configuration. However, as demonstrated with my own Fedora/Postfix setup, the process is manageable and the benefits are significant. I did intentionally not mention the specific versions for the packages since the version numbers will surely change shortly after I publish this. I also did this manually for two of my lesser used domains, but I highly recommend doing this via IAC to ensure consistency and reproducability. I presented it here with multiple edits to the configuration files, but this can be reduced once you have the sum of the changes for each config file.

Taking these steps provides a robust defense for your domains, bolstering your email's trustworthiness and shielding recipients from potential threats. This has eliminated quite a bit of spam and phishing messages from being delivered to my mailbox. In addition to reducing my risk, I am also protecting the repution of my personal domains.

Previous Post