Hands-on: implementing SPF, DKIM and DMARC in Postfix
It's laborious, but secure and scalable
It's laborious, but secure and scalable
This article describes the installation and configuration of the internet security standards SPF, DKIM and DMARC in the Postfix mail server. The three standards protect against phishing, spam, viruses and other malware by securing the sender (e-mail address/domain), the sending host (mail system) and the authenticity (contents) of an e-mail message. The set-up described in this article involves a CentOS 8.1 Linux server running a fully functional standard installation of Postfix, and a straightforward zone file for the DNSSEC-enabled domain example.nl.
We describe how to enable each of the following with such a set-up: • Publication of SPF records • Validation of SPF • Signing with DKIM • Publication of DKIM records • Validation of DKIM • Publication of DMARC records • Validation of DMARC Where relevant, various options are presented (albeit restricted to ready-to-use packages available from the repositories for CentOS) and the factors influencing the choice of option are considered. Configuration examples are included with the explanatory material about how the standards work. As will be apparent from the length of this article, the Postfix configuration process is quite involved. However, the payback for your efforts is a robust, secure and scalable mail system that conforms to the latest internet standards. What's more, domain-level mail security is an important enabler for transitioning from IPv4 to IPv6, because IP address-based filtering isn't possible with IPv6.
The SPF, DKIM and DMARC protocols protect against phishing, spam, viruses and other malware by securing the sender (e-mail address/domain), the sending host (mail system) and the authenticity (contents) of an e-mail message. Their deployment implies modifying the way that the server processes messages and adding various records to the information you publish via the DNS. Although signing with DNSSEC isn't strictly necessary (i.e. required by the standard), it's a valuable additional procedure. DKIM, SPF and DMARC represent the first practical application of the cryptographically secured infrastructure built up over the last decade using DNSSEC. The three standards are normally used together, but they don't have to be. You can use DKIM or SPF on its own, or DKIM and SPF together without DMARC. For that reason, the standards and their implementation are considered below individually. Each of the resulting configurations is a standalone solution capable of operating independently from the others.
Basic knowledge of the SMTP and mail protocols, and of the UNIX operating system, is required for interpretation of the following. When performing the SMTP handshake, the sending mail server states the IP address it will use to connect to the receiving mail server, plus the following information:
With the 'EHLO'/'HELO' command: the host name of the sending mail server
With the 'MAIL FROM' command: the sender/return address of the message
With the 'RCPT TO' command: the addressee(s) for whom the message is intended
In the 'From' header: the sender name and address (display name) to be displayed to the receiving user
And this is how the three security standards confirm (validate) that information:
SPF: validates the 'EHLO'/'HELO' and 'MAIL FROM' information (the mail envelope)
(Optional) check the 'EHLO'/'HELO' host name to verify that the associated IP address resolves to the client's IP address, thus confirming that the identity of the client (the sending mail server) is as claimed
(Mandatory) check the sending domain in the 'MAIL FROM' address to verify that the client's IP address is authorised to send mail from the domain in question, thus confirming that the client (the sending mail server) is has the authority to send/forward messages for the sender
If no 'MAIL FROM' information is available (typically for bounce messages), SPF falls back on the 'EHLO'/'HELO' host name in preference to the sending domain
The (positive) result is a validated mail envelope
Validation is performed as early as possible in the mail pipeline (on the MX gateway), so that, in the event of a hard fail, the software can return error messages via the active SMTP connection
If validation of a message/envelope fails, the connection/message can be blocked immediately, or the message can be forwarded with the negative result in a 'Received-SPF' and/or 'Authentication-Results' header
DKIM: signs outbound mail messages and their headers, and validates the signatures on incoming messages
Check the validity of the digital signature accompanying the message (in the 'DKIM-Signature' header), using the public key published in the signing domain specified in the header
Since the signing domain is not necessarily the sending domain specified in the 'MAIL FROM' or 'From' header, DKIM initially merely confirms that the signing domain is responsible for initiating the message exchange
Outbound messages are signed as late as possible in the sending mail pipeline (the final hop on your infrastructure), so that the signature covers as may headers as possible; signatures on incoming messages are validated as early as possible in the receiving pipeline (the first hop on your infrastructure), so that the addition or modification of headers has as little impact as possible on the validation process
DKIM does not block messages; the result of the validation process is added to the message in an 'Authentication-Results' header and, for example, reflected in a spam filtering score or in a DMARC evaluation
DMARC: processes the results of the SPF and/or DKIM validation on the basis of a policy published by the sending domain in the 'From' header
Check whether the sending domain in the 'From' header matches both the sending domain in the envelope (for SPF) and the signing domain (for DKIM); this alignment check connects the SPF/DKIM-validated sending domains and the information in the 'From' header (the display name)
The policy published in the sending domain specified in the 'From' header is used to determine what to do with incoming mail messages if the SPF and DKIM validation results (including alignment results) are negative: allow, block or quarantine
If requested in the policy, aggregated reports and failure reports are sent to the operator of the sending domain; report messages are standardised to enable automated processing
In the following paragraphs, we consider the implementation of SPF, DKIM and DMARC in Postfix, the most widely used mail server after Exim. Configuration of the standards in the Exim server will be considered in a other hands-on article. Together, the two articles will cover more than 90 per cent of the current mail server market. In a previous article, we described how to use DANE, another protocol that builds on the DNSSEC infrastructure, to secure your mail gateway's TLS certificates. There is no interdependency between the SPF-DKIM-DMARC trio and DANE for mail. It doesn't therefore matter whether the trio is implemented first, or DANE for mail. However, the use of DNSSEC is mandatory with DANE, whereas it's merely recommended with SPF, DKIM and DMARC. On our DNSSEC page you'll find hands-on guides to the implementation of DNSSEC signing and validation in Unbound, Infoblox, PowerDNS and BIND. If your systems also support IPv6, implementation of the five standards as described should ensure a 100 per cent score on internet.nl. If you use the modern internet (security) standards described in this article, your mail systems will be fully up-to-date and in compliance with the latest requirements and guidance from the Forum for Standardisation [1, 2], the National Cyber Security Centre (NCSC) [1, 2] and others. In any event, the internet.nl portal is a good tool for determining your current status and for checking as you proceed with the implementation of new internet standards whether everything is working as intended.
This article is based on the CentOS Linux distribution version 8.1, the community edition of Red Hat Enterprise Linux (RHEL). The implementation we describe involves the following software packages:
BIND named (in a chroot jail) version 9.11.4
Postfix version 3.3.1
OpenSSL version 1.1.1c
Dovecot version 2.2.36
Cyrus SASL version 2.1.27
Dovecot Pigeonhole version 2.2.36
Clam AntiVirus (ClamAV) version 0.102.3
SpamAssassin version 3.4.2
Amavis (amavisd-new) version 2.12.0
SPF Policy Server for Postfix version 2.0.2
OpenDKIM version 2.11.0
OpenDMARC version 1.3.2
At the time of writing (June 2020), the listed versions are the latest versions of the packages available from the standard repositories of CentOS version 8.1. The Amavis-new, ClamAV, OpenDKIM and OpenDMARC packages came from the EPEL repository. They are Extra Packages for Enterprise Linux, compiled using Fedora software, the development distribution for RHEL. You can therefore create an exact copy of our set-up in RHEL or Fedora. To enable and install the necessary PowerTools and EPEL repositories, use the following sequence of commands:
dnf config-manager --set-enabled PowerTools dnf install epel-release dnf config-manager --set-enabled epel
In a large production environment, you would normally run ClamAV and SpamAssassin as independent daemons. However, the set-up described here uses the features (libraries) built into Amavis-new. In smaller environments, some independent daemons can also be run locally on a UNIX domain socket.
The basis for our configuration is a fully operational authoritative DNS server for the domain example.nl. We have used this zone file:
$TTL 1d ; ttl is 1 day @ IN SOA dns1.example.nl. dns.example.nl. ( 2020050800 ; serial (date & version) 8h ; refresh every 8 hours 20m ; retry after 20 minutes 4w ; expire after 4 weeks 20m ; negative caching ttl is 20 minutes ) ; DNS name servers IN NS dns1.example.nl. ; primary name server IN NS dns2.example.nl. ; secondary name server ; SMTP mail gateways IN MX 10 mx.example.nl. ; MX gateway IN MX 100 fallback-mx.example.nl. ; fallback MX gateway ; hosts IN A 192.0.2.162 ; server IN AAAA 2001:db8:1192::2:162 ; server (IPv6) www IN CNAME example.nl. ; WWW server ftp IN CNAME example.nl. ; FTP server mx IN A 192.0.2.106 ; mail gateway mx IN AAAA 2001:db8:1192::2:106 ; mail gateway (IPv6) mail IN A 192.0.2.107 ; mail server mail IN AAAA 2001:db8:1192::2:107 ; mail server (IPv6) imap IN A 192.0.2.108 ; IMAP server imap IN AAAA 2001:db8:1192::2:108 ; IMAP server (IPv6) smtp IN A 192.0.2.109 ; SMTP gateway smtp IN AAAA 2001:db8:1192::2:109 ; SMTP gateway (IPv6) ; exterior hosts dns1 IN A 192.0.2.5 ; primary name server dns1 IN AAAA 2001:db8:1192::2:5 ; primary name server (IPv6) dns2 IN A 198.51.100.6 ; secondary name server dns2 IN AAAA 2001:db8:2198::16:6 ; secondary name server (IPv6) fallback-mx IN A 198.51.100.106 ; fallback mail gateway fallback-mx IN AAAA 2001:db8:2198::32:106 ; fallback mail gateway (IPv6)
The above zone is signed with DNSSEC, meaning that a digital signature is attached to each Resource Record Set (RRset) for the domain. That enables the recipient of a DNS response to validate the information it contains -- in other words, to confirm its authenticity. In our case, the signature-containing records and the other DNSSEC records are not included in the zone file. That is because, in a modern configuration, BIND named generates the signed zone, information about which is then published on the internet via the authoritative DNS servers. In another hands-on article, we describe the entire procedure for configuring BIND to automatically sign your domains. On our DNSSEC page, you will also find similar hands-on articles about signing and validation in various other DNS servers and resolvers. As you will see below, the configuration of SPF and DMARC involves the addition of extra records to your DNS configuration, plus some minor changes to the configuration of your mail server. However, because DKIM sends its own digital signatures with outbound messages and validates the signatures on incoming messages, DKIM implementation requires quite significant modifications to your mail server configuration.
Before going further, it may be helpful to provide some background information about the set-up of our Postfix/Amavis combo. That will make it easier to understand where exactly SPF, DKIM and DMARC are intervening. The modifications described are in any case necessary for the actual implementation of the security standards. As you'll see, the configuration of SPF, DKIM and DMARC is relatively straightforward once the necessary preparations have been made. Our configuration is described below in two stages. Further information about our Postfix/Amavis-workflow is provided in the discussion of OpenDKIM.
First, we have set/changed the following options in the basic Postfix configuration (in the file '/etc/postfix/main.cf'). For the precise significance of each option, refer to the relevant man page.
myhostname = mail.example.nl mydomain = example.nl inet_interfaces = all inet_protocols = all mydestination = $myhostname, localhost.$mydomain, localhost mynetworks_style = host alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases receive_override_options = no_address_mappings recipient_delimiter = + home_mailbox = Maildir/ smtpd_banner = $myhostname ESMTP $mail_name local_destination_concurrency_limit = 2 default_destination_concurrency_limit = 5 tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION
The 'NO_RENEGOTIATION' option is supported only from Postfix version 3.4. For older versions use the OpenSSL option value '0x40000000'.
The diagram below shows the workflow following the basic configuration of Postfix. Incoming mail messages are received via the public (well-known) port 25 (SMTP). They are then checked by SpamAssassin before internal forwarding for delivery. Following authentication (via SASL), Port 587 (submission) is available as an SMTP gateway for use by internal users to send messages.
As you can see from the following configuration (in the file '/etc/postfix/master.cf'), we have also added numerous options to the SMTP and submission services in order to protect our mail gateway ports more effectively. On the public-facing port 25, we have enabled various checks on the validity of the information provided to our gateway by the sending server during the SMTP handshake procedure, in the 'HELO'/'EHLO'-, 'MAIL FROM'- and 'RCPT TO' commands.
smtp inet n - n - - smtpd -o smtpd_recipient_restrictions=reject_invalid_helo_hostname, reject_non_fqdn_sender,reject_unknown_sender_domain, reject_non_fqdn_recipient,reject_unknown_recipient_domain, reject_unauth_destination,reject_rbl_client,zen.spamhaus.org, permit -o smtpd_helo_required=yes -o disable_vrfy_command=yes submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_reject_unlisted_recipient=no -o smtpd_client_restrictions=$mua_client_restrictions -o smtpd_helo_restrictions=$mua_helo_restrictions -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions= -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING -o disable_vrfy_command=yes
That immediately brings us to the heart of the problem with the basic (E)SMTP protocol: the sending server can give any information it likes in the 'EHLO'/'HELO' and 'MAIL FROM' commands (which, together with the information in the 'RCPT TO' command, form the envelope of the mail message). It is therefore very easy to spoof the sender's mail address (subsequently used in the 'Return-Path' header) and the sending host name (subsequently used in the 'Received' header).
The only hard information that can be checked at this stage is the IP address used by the sending system (the client) to make contact with our server. The IP address in question is checked against the DNSBL service blacklist maintained by the Spamhaus Project. As you'll shortly see below, the same IP address is used at the same point (gatekeeping) to check whether the sending system is authorised (in the SPF policy) to send messages from the sending domain specified in the 'MAIL FROM' command. The same approach is used to validate the DKIM signatures on incoming messages, That should be done at the earliest possible point in the pipeline (the first hop on your infrastructure), before your own systems add any new headers to incoming messages.
With the configuration of the submission service on port 587, the main things are to ensure that the service is accessible only to authenticated users and that the use of TLS encryption is mandatory for all connections. On this port, that always involves use of the STARTTLS command. IANA did at one stage reserve a TCP port (number 465) for SMTP-over-SSL (SMTPS), but the arrangement was never standardised and the port has since been assigned to a completely different service. The sticking point is that there is no way of specifying in an e-mail address whether transmission should be secured, and a receiving mail system has no way of interacting directly with a sender, other than via a bounce. Consequently, with DANE/StartTLS, the aim is to secure transmission 'wherever possible'. The configuration of DANE for mail is described in another article. In that article, we also consider the scope for increasing the security of Postfix by utilising the TLS cryptography settings (hardening).
In the following diagram, you can see what our mail message workflow looks like following the installation and configuration of Amavis-new. This mail filter forms the glue (middleware) between the Postfix MTA (Message Transfer Agent) and tools such as virus scanners (in our example ClamAV), spam filters (SpamAssassin) and other filters.
Although most MTAs, including Postfix, feature built-in (native) support for ClamAV and SpamAssassin, the use of an external mail filter is advisable for reasons of flexibility and scalability. Furthermore, Amavis-new is ideal for what we are trying to achieve in this case. The filter can readily be used for SPF, DKIM and DMARC validation, and for signing outbound mail messages in accordance with DKIM.
A CentOS HowTo document for Amavis-new is available online. However, it is seriously out of date. The options that we have set in the '/etc/amavisd/amavisd.conf' configuration file are as follows:
$mydomain = 'example.nl'; $enable_dkim_verification = 1; $enable_dkim_signing = 1; $sa_tag_level_deflt = -999; virus_admin_maps => ["postmaster\@$mydomain"], spam_admin_maps => ["postmaster\@$mydomain"], $virus_admin = "postmaster\@$mydomain"; $mailfrom_notify_admin = "postmaster\@$mydomain"; $mailfrom_notify_recip = "postmaster\@$mydomain"; $mailfrom_notify_spamadmin = "postmaster\@$mydomain"; $recipient_delimiter = '+'; $myhostname = 'mail.example.nl'; $notify_method = 'smtp:[127.0.0.1]:10025'; $forward_method = 'smtp:[127.0.0.1]:10025'; $final_spam_destiny = D_DISCARD; $spam_quarantine_to = undef; # Do nothing with Spam
Note that the DKIM options '$enable_dkim_verification' and '$enable_dkim_signing' are enabled by default. However, Amavis will not be able to perform DKIM signing until the corresponding cryptographic keys have been configured. Moreover, nothing at all will happen until Postfix is configured to forward mail messages to the Amavis service. Some of the above options are considered in more detail later in this article.
In order to ensure that, on completion of the initial checks, Postfix does actually forward mail messages that arrive on port 25 to Amavis, we add the following line to the smtp/25 service in the '/etc/postfix/master.cf' configuration file:
-o content_filter=smtp-amavis:[127.0.0.1]:10024
Because spam filtering will now be done by Amavis, we also comment out the 'spamassassin' option, as follows:
# -o content_filter=spamassassin
In the Amavis configuration (in the '/etc/amavisd/amavisd.conf' file), you'll see that, by default, Amavis is configured to listen to port 10024:
$inet_socket_port = 10024; # listen on this local TCP port(s)
In the configuration set out above, we have specified that, after processing, messages should be delivered to Postfix on port 10025 (like notification messages from Amavis itself):
$notify_method = 'smtp:[127.0.0.1]:10025'; $forward_method = 'smtp:[127.0.0.1]:10025';
Finally, we complete the circle by defining a (local) new service for Postfix on port 10025. From there, incoming messages (delivered by Amavis) can be delivered to the recipient by Postfix without further checks. The service is defined by adding the following lines to the file '/etc/postfix/master.cf':
# Amavis smtp-amavis unix - - n - 2 smtp -o smtp_data_done_timeout=1200 -o smtp_send_xforward_command=yes -o disable_dns_lookups=yes -o max_use=20 127.0.0.1:10025 inet n - n - - smtpd -o content_filter= -o local_recipient_maps= -o relay_recipient_maps= -o smtpd_restriction_classes= -o smtpd_delay_reject=no -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions=reject_unauth_pipelining -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8 -o smtpd_error_sleep_time=0 -o smtpd_soft_error_limit=1001 -o smtpd_hard_error_limit=1000 -o smtpd_client_connection_count_limit=0 -o smtpd_client_connection_rate_limit=0 -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters -o local_header_rewrite_clients= -o smtpd_milters=
Amavis is started as follows:
systemctl start amavisd.service systemctl status amavisd.service systemctl enable amavisd.service
The lengthy basic set-up process is now complete, and we are ready to implement SPF, DKIM and DMARC.
SPF stands for Sender Policy Framework, a protocol that prevents MX gateways accepting mail messages from unauthorised systems. It involves the owner of a mail domain publishing a list of valid sending addresses via the DNS. The listed addresses are typically the SMTP gateways that end users must use for their outbound messages (via port 587), but may also include the addresses of external service providers that send marketing mail for the organisation. Receiving gateways can check (validate) incoming messages against the published address list and refuse any that come from unlisted addresses. That implies adding the following record to the zone file for example.nl presented above; the record is added somewhere after the origin '@':
IN TXT "v=spf1 mx a ip4:192.0.2.109 ip6:2001:db8:1192::/64" " a:senders.example.nl a:senders.bizbooster.com -all"
One sometimes sees a specific SPF record (with exactly the same content) in addition to a TXT record. Such records are a legacy from the early days of SPF. However, the current standard (RFC 7208) says that the SPF record type should no longer be used. If no 'MAIL FROM' information is available (typically for bounce messages), SPF falls back on the 'EHLO'/'HELO' host name in preference to the sending domain. That is therefore stated in a separate SPF record, as follows:
mail IN TXT "v=spf1 a -all" ; SPF record for fallback to EHLO hostname
Turning to the content of the record itself, we see that the version indicator, 'v=spf1', is followed by a typical SPF policy: first a list of systems that are authorised to send mail for the domain, then '-all', which means that all other systems are not authorised. The alternative to ending the record with '-all' is to end with '~all'. That is known as a 'soft fail', meaning that messages from non-validating systems should not be blocked, but forwarded with a tag. This crops up again in the discussion of DMARC. While an 'a' or an 'mx' on its own indicates that the systems specified in the domain's relevant records are authorised, a policy such as 'a:senders.bizbooster.com' enables you to delegate specification of the authorised servers to another party or parties. So, for example, you can authorise a bulk mailing service provider to mail your customers on your behalf (i.e. to send mail that appears to originate from your domain). You could take this a step further by using 'include:bizbooster.com', thus telling receiving gateways to treat the SPF policy specified for bizbooster.com as part of the SPF policy for your domain. The benefit of an arrangement like that is that your service provider doesn't have to ask you to update your DNS configuration every time they change the addresses used for their mail infrastructure. A list of the various policy elements that can be used is provided here; otherwise refer to the source, RFC 7208. As stated in the RFC, 'a' covers both the IPv4 addresses in the A records and the IPv6 addresses in the AAAA records.
SPF validation should take place as early in the workflow as possible, i.e. in the context of the SMTP service (on port 25) operated by Postfix (on the MX gateway). That way, incoming connections from unauthorised systems can be dropped immediately after transfer of the mail envelope, and an error message can be sent back as part of the existing SMTP session. The diagram below shows where the SPF check will be added to the workflow.
We define our SPF policy using the SPF Policy Server for Postfix (available from the EPEL repository). To install the software, simply use this command:
dnf install pypolicyd-spf
The configuration file that comes with the software ('/etc/python-policyd-spf/policyd-spf.conf') is somewhat basic, but will suffice for now:
debugLevel = 1 TestOnly = 1 HELO_reject = Fail Mail_From_reject = Fail PermError_reject = False TempError_Defer = False skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
A more comprehensive, annotated configuration template can be found in the file '/usr/share/doc/pypolicyd-spf/policyd-spf.conf.commented'. However, if you want a more complex configuration, it may be worthwhile looking at the SPF Engine, in which the same author builds on the SPF Policy Server.
In order for incoming messages to be validated by the SPF Policy Server, we first need to add the SPF Policy Server to our Postfix configuration as a new service (in '/etc/postfix/master.cf'):
# SPF Policy Server for Postfix policyd-spf unix - n n - 0 spawn user=policyd-spf argv=/usr/libexec/postfix/policyd-spf
Because the service can't be run by the 'root' or 'postfix' user, we need to create a new user, called 'policyd-spf':
useradd -c "SPF Policy Server for Postfix" -d /etc/python-policyd-spf -s "/sbin/nologin" policyd-spf
We can then add the new service to the checks performed in the context of the SMTP handshake on port 25. That's done by first adding the following option to the file '/etc/postfix/main.cf':
# policyd-spf service policyd-spf_time_limit = 3600
And then adding this option to the smtp/25 service in the file '/etc/postfix/master.cf':
-o smtpd_relay_restrictions=check_policy_service,unix:private/policyd-spf,permit
Note: most online guidance on implementing SPF validation in Postfix recommends adding the following options to the file '/etc/postfix/main.cf':
# policyd-spf service policyd-spf_time_limit = 3600 smtpd_recipient_restrictions = reject_unauth_destination, check_policy_service unix:private/policyd-spf
However, a configuration like that will not work with our set-up, where Amavis is included in the workflow: we don't want the option 'smtpd_recipient_restrictions' applied to all smtpd-based services (including submission/587 and 127.0.0.1:10025), only to smtp/25. That option is in any case gradually being replaced by the option 'smtpd_relay_restrictions'. Because the newer option is effective at a later stage in the SMTP handshake, more information about the mail envelope is by that point available (for writing to the log).
Use the 'postconf (-d)' command to retrieve Postfix's full (default) configuration.
After restarting Postfix, we can check whether SPF validation is in fact taking place by looking at the headers of an incoming mail message. The 'Received-SPF' header should be present, as in the following test message submitted by the system sender.afzenderdomein.nl. The reported result ('Pass') tells us that the system in question is indeed authorised for afzenderdomein.nl.
systemctl restart postfix.service
Return-Path: <tester@afzenderdomein.nl> X-Original-To: ontvanger@example.nl Delivered-To: ontvanger@mail.example.nl Received: from localhost (localhost [127.0.0.1]) by mail.example.nl (Postfix) with ESMTP id A1A58280526E for <ontvanger@example.nl>; Tue, 12 May 2020 15:30:10 +0200 (CEST) X-Virus-Scanned: amavisd-new at example.nl Received: from mail.example.nl ([127.0.0.1]) by localhost (mail.example.nl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id WRX9GtYYGLmm for <ontvanger@example.nl>; Tue, 12 May 2020 15:30:08 +0200 (CEST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=203.0.113.109; helo=sender.afzenderdomein.nl; envelope-from=tester@afzenderdomein.nl; receiver=<UNKNOWN> Received: from sender.afzenderdomein.nl (sender.afzenderdomein.nl [203.0.113.109]) by mail.example.nl (Postfix) with ESMTP id E02892800240 for <ontvanger@example.nl>; Tue, 12 May 2020 15:30:07 +0200 (CEST) Date: 12 May 2020 15:30:06 +0200 To: ontvanger@example.nl Subject: Test SPF validatie Message-ID: <bcfae90dce9c64d9c651570120470b02@sender.afzenderdomein.nl> Errors-To: tester@afzenderdomein.nl From: Tester <tester@afzenderdomein.nl> Reply-To: tester@afzenderdomein.nl
In order to check that non-validating mail messages are flagged up correctly, you can use this online tool to send unauthorised messages.
Please note: In February 2024, we were alerted to a problem involving a large Dutch access provider's SPF records. The company's 'include' statement contained a long list of IP addresses for import. The libspf2 library used by Exim was apparently unable to process the whole of the long TXT record, and therefore truncated the list of IP addresses/blocks after 64 items. The addresses/blocks after the truncation point were consequently not taken into account for validation purposes. As a result, mail from systems whose addresses had been dropped was erroneously refused. It may be that the same problem occurs in Postfix (combined with libspf2), but we have not investigated that possibility.
Diagnosing errors of that kind is very labour-intensive. We are therefore highlighting the problem, so as to save other mail managers a lot of time.
In light of the incident, we advise DNS operators to split their SPF records into separate TXT records containing no more than 64 IP addresses/blocks. The access provider in question has also been informed about the issues associated with its SPF records.
With DKIM, short for DomainKeys Identified Mail, a digital signature is added to the body and header of every outbound mail message. The relevant public key is published via the DNS, so that receiving MX gateways can verify (validate) the digital signatures on incoming messages. By attaching a DKIM signature, a sending host (signing domain) accepts responsibility for initiating the exchange of a message. If the sending host has a good reputation, the acceptance of responsibility increases the 'deliverability' of the message. In other words, it reduces the risk of the message being interpreted as spam. DKIM doesn't normally block messages itself. DKIM merely provides validation results for interpretation by spam filters such as SpamAssassin. Furthermore, DKIM is explicitly not intended as a means of assuring message authenticity, integrity or confidentiality. That requires the user-level implementation of end-to-end encryption. DKIM is standardised in RFC 6376, 8301 and 8463.
With our set-up, there are two ways of integrating DKIM within the workflow. The first involves using Amavis-new's inbuilt DKIM engine. Alternatively, a more scalable (modular) solution can be achieved using OpenDKIM, a milter that is also available from the EPEL repository. Both approaches are described below. Regardless of which option you choose, the DKIM signing of outbound mail messages from internal users requires extension of the workflow to support the submission/587 service. The following diagram illustrates how we splice in an additional Amavis-based service, much as we did for the smtp/25 service, but in this case on internal port 10026. The new service then scans and filters outbound messages, before signing them in accordance with DKIM.
The first step in our reorganisation of the workflow is to configure Amavis to listen to port 10026 as well. That involves placing the following enabling statement in the configuration file '/etc/amavisd/amavisd.conf':
#$inet_socket_port = 10024; # listen on this local TCP port(s) $inet_socket_port = [10024,10026]; # listen on multiple TCP ports
Immediately below the statement, you will see that, on port 10026, the policy bank 'ORIGINATING' is predefined for the processing of mail messages from internal users:
# it is up to MTA to re-route mail from authenticated roaming users or # from internal hosts to a dedicated TCP port (such as 10026) for filtering $interface_policy{'10026'} = 'ORIGINATING';
$policy_bank{'ORIGINATING'} = { # mail supposedly originating from our users originating => 1, # declare that mail was submitted by our smtp client allow_disclaimers => 1, # enables disclaimer insertion if available # notify administrator of locally originating malware virus_admin_maps => ["virusalert\@$mydomain"], spam_admin_maps => ["virusalert\@$mydomain"], warnbadhsender => 1, # forward to a smtpd service providing DKIM signing service forward_method => 'smtp:[127.0.0.1]:10027', # force MTA conversion to 7-bit (e.g. before DKIM signing) smtpd_discard_ehlo_keywords => ['8BITMIME'], bypass_banned_checks_maps => [1], # allow sending any file names and types terminate_dsn_on_notify_success => 0, # don't remove NOTIFY=SUCCESS option };
We modify the policy bank as follows:
# notify administrator of locally originating malware #virus_admin_maps => ["virusalert\@$mydomain"], #spam_admin_maps => ["virusalert\@$mydomain"], virus_admin_maps => ["postmaster\@$mydomain"], spam_admin_maps => ["postmaster\@$mydomain"],
#forward_method => 'smtp:[127.0.0.1]:10027', forward_method => 'smtp:[127.0.0.1]:10025',
The last of those changes is the most significant. Because we are initially going to use the inbuilt DKIM engine for signing, we need to set things up so that, once messages from our internal users have been scanned, filtered and signed, Amavis immediately forwards them to the trusted Postfix service on port 10025. In order to complete the workflow for internal messages, we now need to arrange for messages submitted to Postfix's submission/587 service to be forwarded to the Amavis ORIGINATING service on port 10026. That is done by adding the following option to the submission/587 service in the file '/etc/postfix/master.cf':
-o content_filter=smtp-amavis:[127.0.0.1]:10026
We saw earlier that the following two options -- for DKIM validation and signing -- are enabled by default in the Amavis configuration (in the file '/etc/amavisd/amavisd.conf'):
$enable_dkim_verification = 1; $enable_dkim_signing = 1;
However, signing isn't possible in practice until cryptographic keys are available for generating the signatures. Before going any further, we can check that no keys are yet present using the following command:
amavisd -c /etc/amavisd/amavisd.conf showkeys
The first step in the DKIM configuration process is therefore to generate a cryptographic key pair, as follows:
[root@system ~]# amavisd -c /etc/amavisd/amavisd.conf genrsa /etc/amavisd/example.nl.dkim20200512.pem 2048 Private RSA key successfully written to file "/etc/amavisd/example.nl.dkim20200512.pem" (2048 bits, PEM format)
If you now look in the Amavis configuration directory '/etc/amavisd/', you'll see the key file 'example.nl.dkim20200512.pem'. The file contains a key pair consisting of a private key and a public key (in PEM format). The exact content is not very important in the present context, but you should make sure that the key file access rights provide appropriate security:
chmod 640 example.nl.dkim20200512.pem chown root:amavis example.nl.dkim20200512.pem
Although generating a longer key (4096 bits, rather than 2048 bits) is an option, DKIM signatures remain valid for relatively short periods (in the example below, ten days). They are, after all, used exclusively for delivering messages, which, even in the worst-case scenario, only takes a few days. Restricting the key length to 2048 bits allows DNS traffic to go via the efficient UDP protocol, whereas it would be necessary to switch to the more onerous TCP protocol if longer keys were used. Take care not to use the insecure SHA-1 algorithm (for the reasons explained in RFC 8301). RFC 8463 defines the use of ECDSA for DKIM. We have previously set out the advantages of using this modern cryptographic algorithm for DNSSEC in this Q&A. However, at the time of writing (June 2020), we believe it is too soon to use ECDSA for DKIM. If you sign your mail with a new standard that is not supported by all DKIM-validating MX gateways, you run the risk that your messages will not be delivered.
Next, our newly generated key pair is added to Amavis by inserting the following configuration lines into the file '/etc/amavisd/amavisd.conf':
dkim_key( 'example.nl', 'dkim20200512', '/etc/amavisd/example.nl.dkim20200512.pem' ); @dkim_signature_options_bysender_maps = ( { "example.nl" => { d => 'example.nl', a => 'rsa-sha256', c => 'relaxed/simple', ttl => 10*24*3600 } } );
The first part of the configuration text above defines the key pair itself. After that, the details for the digital signatures are defined. As you'll see, our example makes use of the rsa-sha256 algorithm and the signatures have a validity period of ten days (10*24*3600 seconds), which is ample for the delivery of a mail message. You'll shortly see that the 'DKIM Signature' header includes the start and end times of the validity period in the 't=' and 'x=' tags, respectively. There is also an 'n=' tag for annotating the signature. Use of the 'l=' tag (body length count) is inadvisable. If the body length covered by a signature is restricted, any body content that exceeds the stated length will be vulnerable to manipulation.
The option "c => 'relaxed/simple'" specifies what approach should be taken when generating and checking the digital signature on, respectively, the header and body sections of a message. Certain features of a message, particularly the spacing/hyphenation, are sometimes modified in transit. Consequently, a strict checking policy can lead to problems, because of the way that cryptographic hash functions work. Hashes are digital abbreviations, which are essential for signing. And the modification of a single bit in a message yields a completely different hash, and therefore an invalid signature. To get around that problem, you can convert your message headers and body to a standard format (a normal form) before attaching the signature. Providing that the recipient does likewise, they will get exactly the same hash. Your signature is therefore much less likely to be invalidated by in-transit modification of the spacing/hyphenation in the message. The approach is comparable to a programmer checking the match between two words by first converting them both to lower-case letters. Needless to say, conversion to standard format (canonicalisation) requires additional computation power. RFC 6376 2 accordingly defines various conversion levels: 'relaxed' is more resource-intensive than 'simple', but more likely to result in your signature being accepted by the recipient. We therefore use 'relaxed' for our message headers, but the less resource-intensive 'simple' conversion for the body. Clearly, the option is relevant mainly for organisations that process large volumes of mail. For similar reasons, we previously defined the following option in the Amavis ORIGINATING policy bank. By filtering out eight-bit characters (and restricting ourselves to ASCII), we reduce the danger of messages being corrupted en route to the receiving mail server.
smtpd_discard_ehlo_keywords => ['8BITMIME']
If you want to use the same key pair for multiple sending domains passing through a single DKIM pipeline, you can use a configuration such as the following:
dkim_key( 'example.nl', 'dkim20200512', '/etc/amavisd/example.nl.dkim20200512.pem' ); @dkim_signature_options_bysender_maps = ( { "example.nl" => { d => 'example.nl', a => 'rsa-sha256', ttl => 10*24*3600 }, "anotherexample.nl" => { d => 'example.nl', a => 'rsa-sha256', ttl => 10*24*3600 }, } );
Or a catch-all such as this:
@dkim_signature_options_bysender_maps = ( { "." => { d => 'example.nl', a => 'rsa-sha256', c => 'relaxed/simple', ttl => 10*24*3600 } } );
Be aware that a set-up like that involves using a single signing domain for multiple (distinct) sending domains. Because validation is always based on your signing domain ('d=example.nl;'), the associated DKIM record needs to be linked to that domain only.
If you want each sending domain to use its own key pair and its own signing domain, you need to create a separate DKIM record for each domain. The Amavis configuration is then as follows:
dkim_key( 'example.nl', 'dkim20200512', '/etc/amavisd/example.nl.dkim20200512.pem' ); @dkim_signature_options_bysender_maps = ( { "example.nl" => { d => 'example.nl', a => 'rsa-sha256', c => 'relaxed/simple', ttl => 10*24*3600 } } ); dkim_key( 'anotherexample.nl', 'dkim20200516', '/etc/amavisd/anotherexample.nl.dkim20200516.pem' ); @dkim_signature_options_bysender_maps = ( { "anotherexample.nl" => { d => 'anotherexample.nl', a => 'rsa-sha256', c => 'relaxed/simple', ttl => 10*24*3600 } } );
Whether such a configuration results directly improved deliverability is hard to say. As indicated in the discussion of old DKIM records below, multiple attempts have been made to strengthen the link between sending domain and signing domain. Furthermore, if you want to use DMARC to secure your 'From' sending domain, each sending domain needs to be set up as its own signing domain anyway.
After restarting Amavis, we can verify the availability of our new key pair by using the 'showkeys' command:
systemctl restart amavisd.service
[root@system ~]# amavisd -c /etc/amavisd/amavisd.conf showkeys ; key#1 2048 bits, s=dkim20200512, d=example.nl, /etc/amavisd/example.nl.dkim20200512.pem dkim20200512._domainkey.example.nl. 3600 TXT ( "v=DKIM1; p=" "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt0RLit2O7Jp2QvDHqMZJ" "3ZS08pzLj2PUY3QS1LTeVW6dTJ6pKTF/FbW+bikNPnCCPPNsep1lBcwqBNBvin/m" "RqMlSThQ9l/EutGodrxmZWNzOfcYEeV8B9e2yby8yJUDiLSV5w36WFo/ad/CeLeZ" "Pfz5pFgje8thRNAcWfopXIhRGK6cg9DrX0wHFpmFS8zjGoqKtpaDK3aqAuzVGTz9" "H/at+lcZCY0dBB29EPaSWWZ2uvUHJMRJUZGvCHvyDOZUEf8HX+5VLdYAW2eQLeZ5" "waL3+xYnyYtiZKhauetP/KECAwEAAQ==")
And that provides us with the DNS record that we need to include in the signing domain's zone file. Note the 'dkim20200512': that is the 'selector', which specifies the key pair used for signing. As you'll see shortly, the selector is also included in the 'DKIM Signature' header, so that when the receiving mail server follows the validation procedure, it knows exactly which public key to request from the DNS. The use of selectors enables you to have multiple systems sending DKIM-signed messages for your domain, without the need for those systems to exchange private key files. Each sending mail system can generate its DKIM key pair locally, providing that a unique selector is published in the DNS, along with the relevant public key. Be sure not to use one of the many online tools that generate a DKIM record plus a private key and a public key. Using such a tool implies sharing your private key with the tool provider, enabling them to send mail with your signature.
Before adding the DKIM record above to our zone file, we need to verify that our outbound mail messages (reaching the SMTP gateway via port 587) do indeed bear a valid signature, otherwise they will be blocked by validating MX gateways. The first step is to test the Amavis engine itself, by getting it to sign and validate an internal message:
[root@system ~]# amavisd -c /etc/amavisd/amavisd.conf testkeys TESTING#1 example.nl: dkim20200512._domainkey.example.nl => pass
Next, we send a mail message from our domain through the SMTP gateway, in order to check whether a digital signature is actually being added to the headers. In the following header, that is indeed the case:
Return-Path: <gebruiker@example.nl> X-Original-To: ontvanger@externdomein.nl Delivered-To: ontvanger@mail.externdomein.nl Received: from localhost (localhost [127.0.0.1]) by mail.externdomein.nl (Postfix) with ESMTP id 171E9280526E for <ontvanger@externdomein.nl>; Tue, 12 May 2020 20:43:56 +0200 (CEST) X-Virus-Scanned: amavisd-new at externdomein.nl Authentication-Results: mail.example.nl (amavisd-new); dkim=pass (2048-bit key) header.d=example.nl Received: from mail.externdomein.nl ([127.0.0.1]) by localhost (mail.externdomein.nl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id XkXT1jIpaLjd for <ontvanger@externdomein.nl>; Tue, 12 May 2020 20:43:53 +0200 (CEST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=192.0.2.107; helo=mail.example.nl; envelope-from=gebruiker@example.nl; receiver=<UNKNOWN> Received: from mail.example.nl (mail.example.nl [192.0.2.107]) by mail.externdomein.nl (Postfix) with ESMTPS id E5B952801211 for <ontvanger@externdomein.nl>; Tue, 12 May 2020 20:43:53 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by mail.example.nl (Postfix) with ESMTP id 084AB280526E; Tue, 12 May 2020 20:43:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.nl; h= content-type:content-type:mime-version:user-agent:date:date :message-id:organization:subject:subject:from:from:reply-to :received:received; s=dkim20200512; t=1589309025; x=1590173026; bh=5hbcFvUE8pp3nwVNxbu97zCG2Fh58AtJXA0TwmQaF6k=; b=fG620CxCwUwI jBaTebuq6CIXem9MvRWa9O6FAb/3mei24EZEdPxDJErnzSBfUq6VIsVQRPze/fVp DmsJ5BueHCJF1jVoKv3FkQ+UU9yKhZtGc3uQJbYQmF/gJ+nQ1VVpTbRKAngbWD4A tu3Gev/P57mUPDPAen1kaEDW2P74Q3wf4asDEaKBVpIKKE+9MybEKvHZ9+Y7pbFk aGnVQkb0sWmhR6Szfy6v6rXuaPxQ/fowBcYTLbF4gHcjo26hk8CkvO6BYGBBzbC1 wM50BWCDzHKbgM/fYy6KHzocSBmzRpU= Received: from mail.example.nl ([127.0.0.1]) by localhost (mail.example.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id qvnhJRFJoqHJ; Tue, 12 May 2020 20:43:45 +0200 (CEST) Received: from gebruiker.example.nl (gebruiker.example.nl [192.0.2.129]) by mail.example.nl (Postfix) with ESMTPSA id 430192801211; Tue, 12 May 2020 20:43:45 +0200 (CEST) Reply-To: gebruiker@example.nl To: ontvanger@externdomein.nl From: "Gebruiker" <gebruiker@example.nl> Subject: Test DKIM ondertekening Message-ID: <f0eca619-a8ab-86d4-42e5-5b39f61b813e@example.nl> Date: Tue, 12 May 2020 20:43:44 +0200 Envelope-To: ontvanger@externdomein.nl
You'll see that the 'DKIM Signature' header includes both the signing domain ('d=example.nl;') and the selector ('s=dkim20200512;'). Together, those two pieces of information are sufficient to enable the recipient-side DKIM engine (in our example mail.externdomein.nl) to request the appropriate DKIM record from our DNS server. We can also check the availability of that record as follows:
[user@system ~]# dig TXT dkim20200512._domainkey.example.nl. ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el8 <<>> TXT dkim20200512._domainkey.example.nl. ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19884 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 5 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: e9836ed80afd3232b4a3a8db5ebafe4bd80d43ef7ae55689 (good) ;; QUESTION SECTION: ;dkim20200512._domainkey.example.nl. IN TXT ;; ANSWER SECTION: dkim20200512._domainkey.example.nl. 86400 IN TXT "v=DKIM1; p=" "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt0RLit2O7Jp2QvDHqMZJ" "3ZS08pzLj2PUY3QS1LTeVW6dTJ6pKTF/FbW+bikNPnCCPPNsep1lBcwqBNBvin/m" "RqMlSThQ9l/EutGodrxmZWNzOfcYEeV8B9e2yby8yJUDiLSV5w36WFo/ad/CeLeZ" "c146A8FOWBCvyIh5PI5j2YyRvpzeUTdMLSKcewOPm9xJnHqGkIIbje0xTiSa1WSX" "H/at+lcZCY0dBB29EPaSWWZ2uvUHJMRJUZGvCHvyDOZUEf8HX+5VLdYAW2eQLeZ5" "waL3+xYnyYtiZKhauetP/KECAwEAAQ==" ;; AUTHORITY SECTION: example.nl. 86400 IN NS dns1.example.nl. example.nl. 86400 IN NS dns2.example.nl. ;; ADDITIONAL SECTION: dns1.example.nl. 86400 IN A 192.0.2.5 dns2.example.nl. 86400 IN A 198.51.100.6 dns1.example.nl. 86400 IN AAAA 2001:db8:2198::16:6 dns2.example.nl. 86400 IN AAAA 2001:db8:2198::32:106 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Tue May 12 21:51:39 CEST 2020 ;; MSG SIZE rcvd: 1054
The public key and the other information contained in the record are then used by the receiving mail server to validate the digital signature in the 'DKIM Signature' header. If the validation result is positive, the receiving server accepts the message as authentic (i.e. the message and its content originate from the identified signing domain example.nl). After all, only the DKIM key pair's legitimate owner can publish the public key in the signing domain. And only the legitimate owner has possession of the private key needed to sign the message.
Of course, when you change your DKIM key pair, the public key in the DKIM record needs to be changed as well. That implies performing a minor rollover when you make the change: the new DKIM record needs to be published alongside the old one before the key pair is changed. The new key pair cannot be activated on the server until the TTL has expired and the old DKIM information has been flushed from all DNS caches. If the new key pair is activated too soon, you run the risk that validating mail servers will not accept a DKIM signature based on the new key pair, due to a mismatch with the old information. If you use a new selector for your new key pair, you don't have to wait for all DNS caches to clear before you start using your new key pair. The reason being that no DNS information will have been cached for the new selector. Once you've activated the new key pair, the old DKIM record shouldn't be deleted immediately. Mail can sometimes be in transit for several days before finally being delivered or bounced. We recommend retaining the old DKIM record for at least a week.
The last step in the DKIM configuration process is to set things up so that we can validate the DKIM signatures that other mail servers attach to the messages they send to our users. As we saw earlier, the Amavis option '$enable_dkim_verification' is enabled by default, meaning that incoming messages are ordinarily validated. We can also see that DKIM validation is active by looking at the log file '/var/log/maillog':
May 13 07:56:43 mail amavis[18463]: (18463-11) Passed CLEAN {RelayedOpenRelay}, [203.0.113.109]:42490 [203.0.113.109:1000] <tester@afzenderdomein.nl> -> <ontvanger@example.nl>, Queue-ID: 21675280060A, Message-ID: <87mu6cpecr.fsf@afzenderdomein.nl>, mail_id: fBM7lc39ild4, Hits: -2.102, size: 1963, queued_as: 56E53280526E, dkim_sd=dkim20191104:afzenderdomein.nl, 1159 ms
Amavis performs DKIM validation using the DKIM plugin supplied with SpamAssassin. Consequently, a validation header is added only if a message's spam score is above the threshold defined for spam result reporting. Therefore, if you want the validation results (and the results of all other scans performed by SpamAssassin) added to all incoming mail messages, you need to set a low threshold (in the file '/etc/amavisd/amavisd.conf'):
$sa_tag_level_deflt = -999;
That results in 'X-Spam-Status' and 'Authentication-Results' headers being inserted, as in the following example:
Return-Path: <tester@afzenderdomein.nl> X-Original-To: ontvanger@example.nl Delivered-To: ontvanger@mail.example.nl Received: from localhost (localhost [127.0.0.1]) by mail.example.nl (Postfix) with ESMTP id A1A58280526E for <ontvanger@example.nl>; Tue, 12 May 2020 15:30:10 +0200 (CEST) X-Virus-Scanned: amavisd-new at example.nl X-Spam-Flag: NO X-Spam-Score: -1.65 X-Spam-Level: X-Spam-Status: No, score=-1.65 tagged_above=-999 required=6.2 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HEADER_FROM_DIFFERENT_DOMAINS=0.25, SPF_HELO_NONE=0.001, SPF_PASS=-0.001] autolearn=no autolearn_force=no Authentication-Results: mail.example.nl (amavisd-new); dkim=pass (2048-bit key) header.d=example.nl Received: from mail.example.nl ([127.0.0.1]) by localhost (mail.example.nl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id WRX9GtYYGLmm for <ontvanger@example.nl>; Tue, 12 May 2020 15:30:08 +0200 (CEST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=203.0.113.109; helo=sender.afzenderdomein.nl; envelope-from=tester@afzenderdomein.nl; receiver=<UNKNOWN> Received: from sender.afzenderdomein.nl (sender.afzenderdomein.nl [203.0.113.109]) by mail.example.nl (Postfix) with ESMTP id E02892800240 for <ontvanger@example.nl>; Tue, 12 May 2020 15:30:07 +0200 (CEST) Date: 12 May 2020 15:30:06 +0200 To: ontvanger@example.nl Subject: Test DKIM validatie Message-ID: <bcfae90dce9c64d9c651570120470b02@sender.example.nl> Errors-To: tester@afzenderdomein.nl From: Tester <tester@afzenderdomein.nl> Reply-To: tester@afzenderdomein.nl
As you can see, SpamAssassin's evaluation includes SPF validation. 'Received-SPF' and 'Authentication-Results' are so-called 'trace fields' (as are 'DKIM Signature' and 'Received'). In other words, the headers are added to the top of the header list one at a time during transmission of a mail message (over SMTP). Their content can then be used in the context of subsequent hops. If you trust an SPF evaluation performed at an earlier stage (which will depend mainly on the interim hops), you can reuse the results of that evaluation. However, according to Amavis-new's author, there is little harm in double-checking, because the associated DNS queries are cached anyway. Nevertheless, if you handle a lot of mail, it is worth establishing which hook does what and which trace fields can be reused with confidence.
Anyone who wants a more scalable mail infrastructure is well advised not to use Amavis for DKIM signing and validation, but to use OpenDKIM (or another DKIM provider's software) instead. The OpenDKIM milter is available from the EPEL repository and is installed as follows:
dnf install opendkim
Our first step is to generate a key pair:
cd /etc/opendkim/keys/ opendkim-genkey -b 2048 -h sha256 -s dkim20200516 -d example.nl
That yields two files. First, we have the 'dkim20200516.private' file, which contains the private key. We ensure that the file is readable for OpenDKIM as follows:
chmod 640 dkim20200516.private chown root:opendkim dkim20200516.private
Our second file is 'dkim20200516.txt'. That contains the DKIM record, which should be added to the zone file immediately, as follows:
dkim20200516._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAprCCZ2vGOOH5kAVhuSfXlyx4We" "wtyGfiIZ/ayGuwM6HhjIg0h/imJFBOZgLeoNGLGYIG/ONw/xzZiT2NflukXrqD03DZoZ5ojI" "iAsYVPExcrf0EkDf9G1Lhjyw2Pypo+4m0DUGWSP0w8Ex0is2yTJdaWdKfRSvhKXlt1K20UhA" "Dw8X50P3DqAimWA3GrvUTmEJfLZZ4pdX0pVrOerknM4xQjHr7cOr4OU4yrSGlXu/dfwBGD4X" "sGrcQI2aRPdJJdbuo2kg8HXND+ObpqMHG0yNXDg472LnRKOgNN7Nj02wy+FeFybd9C31PwhE" "3wQUZBu6qNK87nMcHBy/VSMBuK8wIDAQAB" ) ; ----- DKIM key dkim20200516 for example.nl
Our next step is to modify the '/etc/opendkim.conf' configuration file as follows:
Mode sv Socket inet:8891@localhost Statistics /var/spool/opendkim/stats.dat SendReports yes ReportAddress "Example.nl Postmaster" <postmaster@example.nl> SoftwareHeader yes Canonicalization relaxed/simple Domain example.nl Selector dkim20200516 KeyFile /etc/opendkim/keys/dkim20200516.private OversignHeaders From
When you configure OpenDKIM, it's immediately apparent that the software is intended for larger installations: you can easily run separate OpenDKIM servers in parallel for signing and validation, and the default configuration assumes a single sending domain. If you nevertheless want to use OpenDKIM for signing multiple sending domains, you need to add the following two options to the file '/etc/opendkim.conf':
KeyTable /etc/opendkim/KeyTable SigningTable refile:/etc/opendkim/SigningTable
You then generate a separate keyset for each signing domain – in much the same way as described above – and add them to the file '/etc/opendkim/KeyTable'. For each sending domain, signing details need to be included in the file '/etc/opendkim/SigningTable'. Once started up, the OpenDKIM service is available on port 8891.
systemctl start opendkim.service systemctl status opendkim.service systemctl enable opendkim.service
More information about the configuration of OpenDKIM is available from the man page ('man 5 opendkim.conf'). The original configuration template can be found in the file '/usr/share/doc/opendkim/opendkim.conf.sample'.
In order to use the new OpenDKIM service for the validation of incoming mail messages, we need to add it to the Postfix smtp/25 service. The first step is to add the following option to the file '/etc/postfix/main.cf':
# OpenDKIM & OpenDMARC milters milter_default_action = accept
The following option is then added to the configuration of the smtp/25 service (in the file '/etc/postfix/master.cf'):
-o smtpd_milters=inet:127.0.0.1:8891
However, before we restart Postfix, we need to disable DKIM validation by Amavis, which we set up earlier. That involves modifying the option in the file '/etc/amavisd/amavisd.conf' as follows:
$enable_dkim_verification = 0; # disable DKIM signatures verification
Once we restart Postfix, our workflow will be as illustrated below. DKIM validation has now been transferred from Amavis to the smtpd element of Postfix on port 25. The resulting solution is preferable insofar as performing validation earlier in the pipeline reduces the risk of (trace) headers added by our own system causing the digital signatures on incoming mail to be rejected.
If you want to use the OpenDKIM service for signing as well, the first step is to disable Amavis signing by modifying the option in the file '/etc/amavisd/amavisd.conf' as follows:
$enable_dkim_signing = 0; # disable DKIM signing
It's tempting to add the same milter option referred to above to the Postfix submission/587 service (in the file '/etc/postfix/master.cf'). That would result in a workflow as illustrated below, with DKIM signing transferred from Amavis ORIGINATING to the smtpd element of Postfix on port 587.
However, such a solution would be unsatisfactory. As explained earlier, signing needs to take place as late in the pipeline as possible (the final hop on your infrastructure), so that the signature covers as many headers as possible (including those added by Amavis ORIGINATING), thus increasing the deliverability of our signed message.
Our preference is therefore to put the hook to signing by the OpenDKIM service at the end of the pipeline. You'll see that the original configuration file '/etc/amavisd/amavisd.conf' tends towards a set-up like that. With the original configuration, outbound traffic from the Amavis ORIGINATING service is not returned to port 10025 for delivery, but forwarded to port 10027 for DKIM signing. Then, depending on your implementation, either the OpenDKIM service can handle the final delivery (as in the first workflow below), or port 10025 can be used for delivery of the signed mail (as in the second workflow).
The first step in implementation of the first workflow is to add a new smtpd-based Postfix service to port 10027. The configuration can be based on the one used for port 10025, but with the OpenDKIM milter configured on port 8891, as we did for validation:
# OpenDKIM signing 127.0.0.1:10027 inet n - n - - smtpd -o content_filter= -o local_recipient_maps= -o relay_recipient_maps= -o smtpd_restriction_classes= -o smtpd_delay_reject=no -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions=reject_unauth_pipelining -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8 -o smtpd_error_sleep_time=0 -o smtpd_soft_error_limit=1001 -o smtpd_hard_error_limit=1000 -o smtpd_client_connection_count_limit=0 -o smtpd_client_connection_rate_limit=0 -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks -o local_header_rewrite_clients= -o smtpd_milters=inet:127.0.0.1:8891
When we restart Postfix, our new OpenDKIM service is then ready on port 10027 to receive outbound mail messages for signing. Amavis ORIGINATING then needs to be set up so that, instead of sending messages to port 10025, it sends them to port 10027. That implies modifying the configuration in the file '/etc/amavisd/amavisd.conf' as follows:
# forward to a smtpd service providing DKIM signing service forward_method => 'smtp:[127.0.0.1]:10027', #forward_method => 'smtp:[127.0.0.1]:10025',
We now have the workflow illustrated below, and our OpenDKIM-based DKIM implementation is now complete.
In addition to the DKIM records discussed above, you may come across an ADSP record here and there, like the following:
adsp._domainkey IN TXT "dkim=all"
The Author Domain Signing Practices (defined in RFC 5617) were an extension to the DKIM standard, which enabled a domain's owner to indicate that all mail messages from the domain were DKIM-secured. That effectively created a hard link between the sending domain specified in the 'From' header, and the content and authenticity of the message. In much the same way as SPF secures the list of sending mail servers for a given domain in the envelope ('MAIL FROM'), ADSP secures the mail chain for a given sending domain in the 'From' header. However, the ADSP record has now been declared 'historic'. The standard was rarely used and made the delivery of mailing list messages problematic. Another option with a similar purpose was an additional DKIM record, as follows:
_domainkey IN TXT "o=-;"
A record like that told other gateways that all genuine mail messages from the domain in question were signed. However, after appearing in the first DKIM (Sender Signing Policy) drafts, the option was later discontinued and should no longer be used. In the discussion of DMARC below, you'll see that the standard is now used to create a link between the sender address in the 'From' header and the SPF and DKIM-validated sending domains.
DMARC, short for Domain-based Message Authentication, Reporting and Conformance, supplements the other two mail security standards, SPF and DKIM. DMARC lays down a policy (i.e. instructions) telling receiving MX gateways what to do with any incoming mail messages that cannot be validated with either DKIM or SPF. That might involve discarding or quarantining all non-validated mail, for example. DMARC also checks whether the sending domain named in the 'From' header matches the sending domain specified in the envelope (SPF) and the signing domain specified in the DKIM header. DMARC therefore enforces a relationship between the validated SPF and DKIM sending domains and the 'From' sending domain. Consequently, a message that has passed both the SPF and the DKIM sec validation procedures may nevertheless be rejected if the DMARC validation result is negative (indicating a mismatch). A domain's DMARC policy is published via the DNS. In it, a mail domain's operator can specify e-mail addresses to which mail systems can send reports detailing the messages they have accepted and rejected. The operator can then monitor the delivery of both genuine and falsified messages. Note that DMARC implementation requires SPF and/or DKIM (preferably both) to be fully configured for all outbound mail flows. DMARC is defined in RFC 7489.
DMARC introduces nuance to the hard rejection of messages with negative SPF validation results. If the DKIM signature is correct, such a message can be forwarded, albeit tagged. That's particularly important in terms of enabling the use of mailing lists and mail forwarding, where the sender isn't normally specified in the SPF record. If you use DMARC, therefore, you will generally want to modify the SPF record as follows (note the tilde '~' at the end):
IN TXT "v=spf1 mx a ip4:192.0.2.109 ip6:2001:db8:1192::/64" " a:senders.example.nl a:senders.bizbooster.com ~all"
Thus, DMARC (like DKIM) usually serves as a check performed in the context of spam filtering. From the DMARC specification RFC 7489, it's apparent that the security standard is concerned mainly with the security of business mail, with a view to reducing the risk of CEO fraud (whaling), for example.
However, a set-up such as the one we describe here could easily be created for malicious purposes. In itself, therefore, DMARC is not a complete solution. In the context of the spam filter, the decision as to whether a message should be allowed to continue ultimately depends largely on the reputation of the sender (host and sending domain), as reflected in the spam score. Hence, domain-level mail security is an important enabler in the transition from IPv4 to IPv6, where filtering on the basis of IP addresses is not possible.
The first step towards enabling DMARC for our domain example.nl is to add the following DNS record to the zone file:
; DMARC _dmarc IN TXT "v=DMARC1; p=none; sp=none; adkim=s; aspf=s; rua=mailto:dmarc-reports@example.nl; ruf=mailto:dmarc-reports@example.nl; fo=1;"
Let us consider the various elements of the policy in turn:
'v=DMARC1;' We are using DMARC version 1 (currently the only version available)
'p=none;' The policy for the root domain
'sp=none;' The policy for the subdomains (if they don't publish separate DMARC policies of their own)
'adkim=s;' The alignment between the 'From' address and the signing domain specified in the DKIM-validated digital signature
'aspf=s;' The alignment between the 'From' address and the SPF-validated 'MAIL FROM' sending domain
'rua=mailto:dmarc-reports@example.nl;' The mail address that we want aggregated reports sent to (by default once a day, adjustable using the 'ri' tag)
'ruf=mailto:dmarc-reports@example.nl;' The mail address that we want forensic (failure) reports (about individual rejected mail messages) sent to
'fo=1;' Request for receiving mail servers to generate a forensic report for each rejected message
As you can see, we start with the tags 'p=none;' and 'sp=none;'. Those tags tell receiving mail servers to perform all checks, but not to block a message if the SPF and/or DMARC validation result is negative. Other possible values for the tags are 'quarantine' and 'reject'. To begin with, the value is set to 'none' so that valid messages don't get blocked, e.g. because you've forgotten to include an (external) mail server in your SPF policy. The setting is used in a similar way to enable DKIM signing for each valid mail server in turn. Once everything is running smoothly – which you ascertain by reviewing the incoming DMARC reports – you can change the value to 'reject'. That will cause all non-validating messages to be blocked. Opinion differs as to whether you should send out failure reports yourself. Some people argue that sending out bounce messages, some of which will inevitably be for legitimate messages that contain errors, implies sharing potentially sensitive information. The counterargument is that all the information in a failure report has already been transmitted across the entire network on its outward journey, so what you share in a bounce message is already in the public domain. We therefore favour comprehensive mail security based on the use of STARTTLS, DANE and end-to-end encryption.
The tags 'adkim=s' and 'aspf=s;' specify how strict the recipient should be regarding the alignment between the 'From' sending domain stated for an incoming message and the SPF and DKIM-validated domain names. If the 'adkim' tag value is 's' (strict), the 'From' domain must exactly match the signing domain stated in the 'd=' tag of the DKIM header. So, for example, you will not be able send messages from a subdomain (e.g. alerts@news.example.nl) signed with your root domain's digital signature [1, 2] (example.nl). You can do that, however, if you use the tag 'adkim=r;' (where 'r' is 'relaxed', the default value). Note that the use of DKIM implies that the signing domain and your root domain are the same. Also, take care not to confuse this tag with the canonicalisation tag ('c=relaxed/simple;') in the DKIM header. With the 'aspf' tag too, you have a choice between 'strict' and 'relaxed'. However, this tag relates to alignment between the message's 'From' sending domain and the 'MAIL FROM' sending domain stated in the envelope. In other words, while SPF and DKIM enable you to secure your 'MAIL FROM' sending domain and the sending mail servers, DMARC enforces a link with the 'From' header. After all, the sender information (display name) in that header is what the end user ultimately sees in their mail client. The 'adkim=s' and 'aspf=s;' tags are there for fine-tuning the DMARC validation alignment requirements.
It will come as no surprise that you can't use any old e-mail addresses in the 'rua=' and 'ruf=' tags. If you want DMARC reports sent to an e-mail address in a domain other than the one that the policy is for – if you use a DMARC service provider, for example, and want the reports to go to them – the nominated recipient needs to give permission. To that end, the recipient has to insert a DMARC authorisation record in their zone file:
example.nl._report._dmarc IN TXT "v=DMARC1;"
The authorisation record tells receiving mail servers that their DMARC reports for the domain example.nl can legitimately be sent to an address at the other domain in question. For full details and other available tags, see section 6.3 of RFC 7489.
Be sure not to forget the final semicolon ';' at the end of the DMARC Authorization record. The semicolon is mandatory, but was nevertheless omitted from earlier versions of RFC 7489.
Once you've published your DMARC record, you'll see that report messages from the big mail handlers start arriving at your nominated address. We get them from Google, Yahoo, Microsoft, Amazon, Fastmail and various smaller service providers, for example. Dutch government organisations and XS4ALL often send us DMARC reports as well. Each report message is accompanied by a (zipped) XML file containing details of all incoming mail messages. If you wish, you can arrange for a commercial service provider such as DMARC Advisor and DMARC Analyzer to process the data and generate fancy graphics. That involves opening an account with the service provider and modifying your DMARC record so that their e-mail address is the nominated reporting address. As explained above, the service provider will also need to add an authorisation record to their zone file. You'll then get a user-friendly (online) analysis and visualisation of the incoming report messages.
However, various (low-level) open-source tools are now available for processing and analysing DMARC reports:
You might consider using such a tool as a basis for your own analysis and dashboard [1, 2, 3, 4].
If you want to do your own DMARC evaluation for incoming messages (and send reports to third parties), you can use OpenDMARC, which is available from the EPEL repository. Like OpenDKIM, OpenDMARC is an open-source package created by the Trusted Domain Project. OpenDMARC is a milter that can handle both validation (policy enforcement) and reporting (generation). It's installed with the following command:
dnf install opendmarc
Please note: we use OpenDMARC because it's a proven solution that's also available as a standard package for CentOS/RHEL. However, a reader has drawn our attention to dkimpy-milter, a Python-based milter that is more or less compatible with OpenDKIM and supports the modern Ed25519 EdDSA algorithm. We have not investigated this solution ourselves, but nevertheless wish to draw its existence to your attention.
For our set-up, we'll modify the configuration in the file '/etc/opendmarc.conf' as follows:
AuthservID OpenDMARC CopyFailuresTo dmarc-failures@example.nl FailureReports true FailureReportsBcc dmarc-reports-sent@example.nl FailureReportsOnNone true FailureReportsSentBy postmaster@example.nl HistoryFile /var/spool/opendmarc/opendmarc.dat IgnoreAuthenticatedClients true IgnoreHosts /etc/opendmarc/ignore.hosts RejectFailures false ReportCommand /usr/sbin/sendmail -t RequiredHeaders true Socket inet:8893@localhost SoftwareHeader true SPFIgnoreResults true SPFSelfValidate true TrustedAuthservIDs HOSTNAME
The file '/etc/opendmarc/ignore.hosts' needs to be created at this stage as well, because the OpenDMARC daemon won't start without it:
touch /etc/opendmarc/ignore.hosts chown opendmarc:opendmarc /etc/opendmarc/ignore.hosts chmod 664 /etc/opendmarc/ignore.hosts
As you'll see, we start with the setting 'RejectFailures false'. The effect of that setting is that, to begin with, OpenDMARC adds headers containing SPF/DKIM evaluation results to handled messages, but doesn't block any messages. Only once we're sure that everything is running smoothly do we change the option value to 'true'. Incoming mail messages may then be blocked following evaluation on the basis of the sending domain's DMARC policy. The effect of the option 'SPFIgnoreResults true' is that OpenDMARC ignores existing SPF headers and always does its own SPF validation. Alternatively, you can set up OpenDMARC to rely on the results of the first validation by policyd-spf. That's done by making the option value 'false'. OpenDMARC then does SPF validation only if the message doesn't yet have an SPF header. It's also possible to opt for 'SPFSelfValidate false', in which case OpenDMARC will not perform SPF validation and will rely entirely on any SPF headers that the message already has. In that context, it's important to be aware that, for performance reasons, a 'check_policy_service' (part of Postfix's 'smtpd_relay_restrictions' option and 'smtpd_recipient_restrictions' option) is always performed for the 'smtpd_milters'. And the latter is where we will shortly add a hook to OpenDMARC.
The easiest solution would be to disable policyd-spf when using OpenDMARC (in the file '/etc/postfix/master.cf'):
#-o smtpd_relay_restrictions=check_policy_service,unix:private/policyd-spf,permit
However, there are good reasons for keeping policyd-spf in use as a separate scanner for blocking non-validating messages early in the pipeline. You then have more direct control over which messages are and aren't allowed through, instead of being reliant on a DMARC policy published by someone else. Disabling OpenDKIM for DKIM validation at the same point may be similarly undesirable.
Now that the configuration is complete, we can start up our new OpenDMARC service as follows:
systemctl start opendmarc.service systemctl status opendmarc.service systemctl enable opendmarc.service
Next, we add the service (now available on port 8893) to the milters listed for the Postfix smtp/25 service in the file '/etc/postfix/master.cf':
-o smtpd_milters=inet:127.0.0.1:8893
Or, if you don't want to disable OpenDKIM:
-o smtpd_milters=inet:127.0.0.1:8891,inet:127.0.0.1:8893
The most extensive workflow, complete with policyd-spf and OpenDKIM (for validation), now looks like this:
After restarting Postfix, you'll see that incoming mail messages do now have a DMARC header:
Return-Path: <tester@afzenderdomein.nl> X-Original-To: ontvanger@example.nl Delivered-To: ontvanger@mail.example.nl Received: from localhost (localhost [127.0.0.1]) by mail.example.nl (Postfix) with ESMTP id C91EE280526E for <ontvanger@example.nl>; Mon, 18 May 2020 12:39:54 +0200 (CEST) X-Virus-Scanned: amavisd-new at example.nl Received: from mail.example.nl ([127.0.0.1]) by localhost (mail.example.nl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id NrnBGwngyxIY for <ontvanger@example.nl>; Mon, 18 May 2020 12:39:53 +0200 (CEST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=203.0.113.109; helo=afzenderdomein.nl; envelope-from=tester@afzenderdomein.nl; receiver=<UNKNOWN> DMARC-Filter: OpenDMARC Filter v1.3.2 mail.example.nl 922E02800602 Authentication-Results: OpenDMARC; dmarc=pass (p=none dis=none) header.from=afzenderdomein.nl Authentication-Results: OpenDMARC; spf=pass smtp.mailfrom=tester@afzenderdomein.nl DKIM-Filter: OpenDKIM Filter v2.11.0 mail.example.nl 922E02800602 Received: from afzenderdomein.nl (afzenderdomein.nl [203.0.113.109]) by mail.example.nl (Postfix) with ESMTPS id 922E02800602 for <ontvanger@example.nl>; Mon, 18 May 2020 12:39:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=afzenderdomein.nl; s=dkim20191104; h=Content-Type:MIME-Version:Message-ID:Date: Subject:To:From:Sender:Reply-To:Cc:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=6BzxQA/45SnSHmbvysWH67ttw0oekzBPj78KEvNmsN4=; b=SFGwnph4EeON20GdkU/JZKCXK3 /9uHXfe5rAqoLx9mr59xwBHhG11B8f6ZMDyYt+cfa4u/hI5bmh/7ryLU+KPTYP9zLCW8ndBwnGoPm k7Y+4o/kQM1ZbSbMM79Q2D5pkS7nsMt/sBlUZMdEQ9bmD2qNlFWhGr68F0nM+5KQN69g=; Received: from [203.0.113.109:1000] (helo=inauditus) by afzenderdomein.nl with esmtpsa (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from <tester@afzenderdomein.nl>) id 1jadBW-00028K-Qm for ontvanger@example.nl; Mon, 18 May 2020 12:39:52 +0200 From: Tester <tester@afzenderdomein.nl> To: Ontvanger <ontvanger@example.nl> Subject: Test OpenDMARC Date: Mon, 18 May 2020 12:39:50 +0200 Message-ID: <87o8qlr0g9.fsf@afzenderdomein.nl>
Our SPF/DKIM/DMARC set-up is now complete!
You can test your mail configuration on Internet.nl and via this portals:
Although the configuration involves bringing a whole series of new TCP ports into use, only two have to be open for connections from the outside world: 25 (smtp) and 587 (submission). All the other ports (10024, 10025, 10026, 10027, 8891 and 8893) are exclusively for internal/local use. Consequently, the firewall has to remain closed for all those ports. In our set-up, ports 10026 and 10027 were the only local ports whose use led to problems with SELinux. The reason being that they were already assigned to SpamAssassin. We resolved that issue as follows:
semanage port -m -t amavisd_recv_port_t -p tcp 10026 semanage port -m -t amavisd_send_port_t -p tcp 10027
No changes were needed for the other ports:
[root@system ~]# semanage port -l | grep 8891 milter_port_t tcp 8890, 8891, 8893 [root@system ~]# semanage port -l | grep 1002 amavisd_recv_port_t tcp 10026, 10024 amavisd_send_port_t tcp 10027, 10025 spamd_port_t tcp 783, 10026, 10027
In combination with the EPEL repository, CentOS version 8.1 provides everything needed to support a fully featured, scalable and secure Postfix-based mail infrastructure. However, setting up such an infrastructure involves quite a lot of extension and integration work. A modular, open architecture of the kind described has the advantages of being scalable to enterprise-level, and being customisable and dimensionable to your particular requirements. In this hands-on article, we have explained how to realise the comprehensive implementation of all three mail security standards: SPF, DKIM and DMARC. Combining the set-up described with support for IPv6 and DANE should ensure a 100 per cent score on internet.nl, implying that your mail systems are completely up-to-date and consistent with the latest requirements and guidance of the Forum for Standardisation, the NCSC and others. What's more, domain-level mail security is an important enabler for transitioning from IPv4 to IPv6, because IP address-based filtering isn't possible with IPv6.
We'll keep this article updated to reflect new SPF/DKIM/DMARC functionality for Postfix as and when it becomes available. In the meantime, we're pleased to hear any feedback you may have on this article.
If you already use Exim, check out another hands-on article where we discuss the configuration of SPF, DKIM, and DMARC for Exim in a similar way as we did here for Postfix.