
In this fifth article about configuring an email server on RHEL compatible distro AlmaLinux, we provide tools to other servers to protect themselves from emails that claim to come from our domain.
We are talking about: SPF, DKIM and DMARC.
The first two provide the destination email server, tools to check the authenticity of the incoming message, better, to check if the message they are about to receive comes from a server that is authorized to send messages on our behalf.
DMARC provides a policy about how the destination server should react once a message fails SPF and DKIM. Finally DMARC instructs how to report email traffic that is supposed to come from our email server, this providing us a metric about who is abusing our domain name or who abuse our domain name.
Let’s dig a bit deeper of these three tools.
SPF — Sender Policy Framework
This framework instructs the destination server that the “real” emails coming from our domain must come only from the specified IP(s).
From the practical point of view, we set up a record on our DNS that specifies a list of IPv4 and/or IPv6.
This is a practical example:
300 IN TXT "v=spf1 a mx ip4:192.0.2.10 -all"
In this DNS record we specify:
spf1
— SPF version; actually this is the only version;a
— The IP associated with the A record is authorized to send email;mx
— The IP that is indicated to receive email, can also send them;ip4
— The list of authorized IP explicitly indicated to send emails;-all
— The behavior the receiver has to adopt when the incoming email fails the verification;-all
mean: Reject all messages that fail the SPF verification (that don’t come from the specified IPs). These are just recommendations for the destination mailserver.
Your setup from a practical point of view
This is where your experience and knowledge come in play. If the only machine allowed to send email from your behalf is the machine configured in the MX record, the DNS has to be configured in such way:
300 IN TXT "v=spf1 mx -all"
This is valid for the majority of SOHO email servers
Slight variations is about the all
indication; in case you’re just testing the policy you can indicate a soft fail policy: ~all
means: “Don’t refuse it but mark it as suspicious”. Another indication can be: ?all
thats means: “Don’t know what to do; decide by yourself” (neutral policy).
DKIM — DomainKeys Identified Mail
With DKIM, every message coming from your email server, is signed with the domain’s private key and the receiver verifies with the public key that the message has not been altered in transit.
Extra headers will be placed on the message:
DKIM-Filter: OpenDKIM Filter v2.11.0 mail.example.com 64C976007041
Authentication-Results: mail.example.com;
dkim=pass (2048-bit key, unprotected) header.d=notifications.example.net header.i=@notifications.example.net header.a=rsa-sha256 header.s=s1 header.b=kWgne00p
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=notifications.example.net;
h=content-type:from:mime-version:subject:to:cc:content-type:from:
subject:to;
s=s1; bh=nOxErjacMsolza/ZGC1cpwYUt6Z7QCpPXZsMr4/diEc=;
b=kWgne00phqP7hDFVdXcaOi8LIiNcAUha5T/t64JOcICh3SBD5u+bpMbWbZkhG6PYANeB
khZugF+GILNUjaRNisXkKWm6dwfLgeg61h8kC64jguWIH5sYxA0JzANV/tPqZo9bfDi/xb
Rd8i2XDqkCGZjcvwZWfJ+LZrpR+n6eL/J7o9vDbzHAd64+ZDX2qbRh34r5dgnzDtidrOl0
CcMd9/RS0TXEU0VDiGAo3hG3jDn9ybvlo/2WYELOtkjze+BpQw/MRYK0r8GuF7JGKNkMnX
wL5/O/eyUmBa33ss4tvIXfVRfgwe2zoMzbgif5NOvsIWo1v4Eh7PjI8JQMSsDAvA==
DKIM-Filter
— Daemon that verified the signature, version and domain;Authentication-Results
— Result of authentication:pass
means the authentication is OK;DKIM-Signature
— The signature added by the source server.
Your setup from a practical point of view
Install OpenDKIM and OpenDKIM Tools. OpenDKIM is the main daemon that sign and verify messages; OpenDKIM Tools generates the keys needed by OpenDKIM and check the correct propagation of the public key in the DNS.
- Enable crb repository: Required to install the two packages
# dnf config-manager --set-enabled crb
- Install OpenDKIM and OpenDKIM tools:
# dnf install opendkim opendkim-tools
- Add the Postfix user to the OpenDKIM group: Allows the communication between Postfix and OpenDKIM via the OpenDKIM socket at:
/var/spool/postfix/opendkim/opendkim.sock
.
# gpasswd -a postfix opendkim
The installed packages versions are:
Installed Packages
Name : opendkim
Version : 2.11.0
Release. : 0.36.el9
Architecture : x86_64
Size : 552 k
Source : opendkim-2.11.0-0.36.el9.src.rpm
Repository : @System
From repo : epel
Summary : A DomainKeys Identified Mail (DKIM) milter to sign and/or verify mail
URL : http://opendkim.org/
License : BSD-3-Clause AND Sendmail
Description : OpenDKIM allows signing and/or verification of email through an open source
: library that implements the DKIM service, plus a milter-based filter
: application that can plug in to any milter-aware MTA, including sendmail,
: Postfix, or any other MTA that supports the milter protocol.
Name : opendkim-tools
Version : 2.11.0
Release : 0.36.el9
Architecture : x86_64
Size : 139 k
Source : opendkim-2.11.0-0.36.el9.src.rpm
Repository : @System
From repo : epel
Summary : An open source DKIM library
URL : http://opendkim.org/
License : BSD-3-Clause AND Sendmail
Description : This package contains the tools necessary to create artifacts needed
: by opendkim.
- On the directory
/var/spool/postfix
create theopendkim
directory and assign ownership toopendkim
; this directory will contain the OpenDKIM socket.
- Now we have to modify the OpenDKIM setup file at
/etc/opendkim.conf
. Below only the parameters I modified from the default setup.
# Requires relaxed signatures for headers (tolerant for spaces)
# and simple (well: It’s strong!) signature for body;
# a simple space on body fails the signature
Canonicalization relaxed/simple
# Daemon operating mode: Both for (s)ignature and (v)erification
Mode sv
# In case daemon crash, try to restart, max 5 times / hour
AutoRestart yes
AutoRestartRate 5/1H
# This is the standard, recommended algorithm
SignatureAlgorithm rsa-sha256
# User the daemon run under! The socket owner is opendkim:opendkim
UserID opendkim
# Unix socket to communicate with postfix - Be sure the directory exists
Socket local:/var/spool/postfix/opendkim/opendkim.sock
# When a message to sign arrives, this is the first table the daemon
# looks at, looking for signature to be used identified by the selector
SigningTable refile:/etc/opendkim/SigningTable
# Maps domains to the keys used to sign emails
# using the selector as entry point
KeyTable refile:/etc/opendkim/KeyTable
# Ignore these hosts when verifying incoming messages
ExternalIgnoreList /etc/opendkim/TrustedHosts
# Internal hosts authorized to sign the messages
InternalHosts /etc/opendkim/TrustedHosts
- Comment out the following on
opendkim.conf
: We don’t have a default key file.
## Gives the location of a private key to be used for signing ALL messages. This
## directive is ignored if KeyTable is enabled.
# KeyFile /etc/opendkim/keys/default.private
Note: “This directive is ignored if KeyTable is enabled.” — I saw this is not true; if we execute any query with the command: opendkim-testkey
the query fails with an error:
# opendkim-testkey -d example.com -s default -vvv
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: /etc/opendkim/keys/default.private: stat(): No such file or directory
- The file
/etc/opendkim/TrustedHosts
has to contain, as declared by the.conf
file above, the hosts authorized to sign and the hosts that are safe to ignore the signature when a message arrive. The content should be obvious.
127.0.0.1
::1
localhost
- The file
/etc/opendkim/SigningTable
is the entry point for any signature that contains the identification of the key to be used for each domain the server handles; this has to be adapted for your domain name!
*@example.com default._domainkey.example.com
The above means: For each address at the domain name example.com
use the key identified by the selector default
. Selectors are useful when rotating the keys.
The above requires the following directory structure under /etc/opendkim
:
.
├── KeyTable
├── SigningTable
├── TrustedHosts
└── keys
└── example.com
├── default.private
└── default.txt
Note that when the remote server has to verify one email arriving from example.com
will query DNS for the record default._domainkey.example.com
. We will setup this later.
KeyTable
indicates where to find the private key that has to be used to sign the outgoing messages:
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
The line above close the instruction on where to find the private key; so default._domainkey.example.com
is the same indication coming from the SigningTable
, example.com
is the domain to sign, default
is the selector and finally /etc/opendkim/keys/example.com/default.private
is the full path of the private key to be used.
- Assuming that you have created the directory structure as above, so you have the directory
example.com
underkeys
, you can run the following commands to create a key pair:
# opendkim-genkey -d example.com -D /etc/opendkim/keys/example.com -s default -v
This generates the keys for example.com
that will be stored under /etc/opendkim/keys/example.com
with the key selector default
and the command will interact verbosely.
# chown opendkim:opendkim /etc/opendkim/keys/example.com/default.private
# chmod u=r /etc/opendkim/keys/example.com/default.private
The two commands above should be obvious for any linux admin and are pointed to allow the reading of the private key only to opendkim
.
That’s it.
What about the public key?
Oh, yes! We also have a public key under /etc/opendkim/keys/example.com/default.txt
!
What exactly does this file contain?
default._domainkey.piraneo-canepa.ch 300 IN TXT "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtEgKRdnkv0oWuelOK6M9K5AVfsM0Z1xkku18sy40ck9+N8p4+/wv3rRSMVliEp4MfrG4JBKcofFvHwy3JWV3GRGvH5QvL5R1zD+eY8mtxrN2sXApe6qUjKOFjq9k5JEZFlEx4HutDx7gqR0rs3mnkeKToOIyUUkHFbYsp1jsEm3QqPkqqhVVAMiZyUGpnjqdfsLy3GXkGm4tuIBog295GaHIhmmjPIEMGCfRPfgGBk/JXGlKiiZLrYNwdz0en1ETq51D3TbV8LwvlZ53Q6aobdCswB0RlSWbfNqiM3kp0aZp7Tl96DXiIuC7NuN4xdahw9Zpc20JM54XvYiE6BqvCQIDAQAB"
This is the content that has to be published on your DNS! The public key the receiver counterpart has to use to validate your messages!
The above is exactly the format of the DNS record that has to be adapted to your DNS configuration interface.
Integration with Postfix
OpenDKIM and Postfix have to talk to each other; they talk with the milter protocol.
The communication and the behavior between the two has to be setup in /etc/postfix/main.cf
as follows:
# DKIM operations
milter_protocol = 6
milter_default_action = tempfail
smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
Few remarks:
milter_protocol
— Protocol version;6
is the most recent and safe to use with actual releases of Postfix and OpenDKIM;milter_default_action
— What postfix has to do when OpenDKIM fails or is not available? In such case withtempfail
we inform the sender that it has to retry later;smtpd_milters
— Unix socket where Postfix and OpenDKIM talk together when receiving or sending emails;non_smtpd_milters
— Same as above but for locally generated emails (cron, php, …whatever).
DMARC: The end of the story
When everything is done, we have to tell the receiver what to do if something goes wrong; here it is the DMARC DNS record:
_dmarc 300 IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com; ruf=mailto:postmaster@example.com"
With this DNS record we instruct to quarantine the messages that claims to come from our domain in case they fail the above checkings and — periodically — send us two reports:
rua
— A basic resumé of the mail the server handled and the related results; useful to check who is abusing our name;ruf
— A forensic report of all the failures; I’ve never encountered a server that sends these reports!
Leave a Reply