How to configure SPF in Postfix

SPF, or Sender Policy Framework, is a way for domain owners to say: “these are the email servers my domain sends mail from; anyone else is attempting to impersonate me.” These days, a lot of email services implement it in some way (both for receiving and sending email), and, if you administer your own server, it’s quite easy to implement, as we’ll see.

There are actually two parts to SPF: configuring it for your domain (so that your outgoing emails have a better chance of being accepted and emails impersonating your domain have a better chance of being refused), and configuring your email server to check whether incoming emails come from an SPF authorized server or not. Without further ado:

1- Configuring SPF for your domain’s outgoing mail:

This is actually done in DNS, not in the email server. Simply add a TXT record for your domain, with something like this:

v=spf1 a:mailserver1.domain.com a:mailserver2.domain.com -all

There are other configuration parameters, such as getting the servers from the domain’s MX record (add “mx“), and copying another domain’s authorized servers (“include:otherdomain.com“). And, of course, if you only have one server sending mail, you don’t need two “a:” entries.

Pay special attention to the “-all” part at the end: that instructs other servers to refuse any email apparently coming from your domain that isn’t authorized for it. That should ideally be the final configuration, but if you’re doing this for the first time you may want to change it to “~all“, which means “softfail”: it suggests to the other side that the mail is probably not legit, but that it shouldn’t refuse it outright because of it. After testing it for a while, if you see no problems then you can change it to “-all“.

2- Configuring SPF checking for incoming mail in Postfix:

I’ll assume you already have a working Postfix server (hopefully already using TLS encryption, hint hint), and that you want to respect any “-all” configurations (see above) in incoming mail — in other words, if a domain says “all my mail comes from server X, refuse anything else” and you receive an email purportedly from that domain, but from a server Y, it’s refused (for “~all“, it’d generate a warning).

First, install pypolicyd-spf. On a Debian-based system, the package (as of this writing) still has its old name, so enter the following:

apt install postfix-policyd-spf-python

Edit /etc/postfix/master.cf, and add:

# spf
policyd-spf  unix  -       n       n       -       0       spawn
        user=policyd-spf argv=/usr/bin/policyd-spf

Then edit /etc/postfix/main.cf, and look for the “smtpd_recipient_restrictions” section. That section will probably begin by accepting mail from your networks and from authenticated users, then rejecting non-authorized relaying, possibly followed by some white- or blacklists, and maybe a couple of filters, finally ending with “permit“. Before that “permit“, add:

    check_policy_service unix:private/policyd-spf,

(note the comma at the end; without it, Postfix wouldn’t read the rest of that section.)

IMPORTANT: while you’re still in testing, it may be a good idea to add “warn_if_reject” to the beginning of the previous configuration line. With it, Postfix will still log SPF failures, but won’t actually reject an email because of it. When you’re certain everything is fine, you can then remove that prefix (and then reload Postfix, of course).

Add also the following to that configuration file, outside of any section:

policy-spf_time_limit = 3600

And you’re done. Reload or restart Postfix, and check your logs to see if everything is working as intended.

Note that other tools, such as SpamAssassin, may also use SPF to weigh whether an incoming mail is more or less likely to be spam. That, however, is unrelated to the Postfix configuration above, which does a binary “block/don’t block”.

How to configure TLS encryption in Postfix

Although Postfix (and the SMTP protocol in general) can function without any kind of encryption, enabling TLS it can be a good idea in terms of both security and privacy, so let’s look at how it can be easily done.

We’ll actually be configuring two separate types of encryption:

  • Opportunistic encryption for regular SMTP (port 25), both incoming 1 and outgoing 2. “Opportunistic”, here, means that the server will ask for encryption and use it if the other side also supports it, but if it doesn’t then it’ll work without encryption. Although forcing it might sound tempting for security reasons, in reality many “smaller” servers around the world still don’t support it, so you would be unable to send or receive mail to/from those. Opportunistic TLS at least means that most “big” email services (e.g. Gmail) will communicate with you (and you with them) with encryption.
  • Forced encryption for the submission service (port 587). This is used by your own users (even if it’s just you) to send mail through your server, and typically has different restrictions (e.g. it’s authenticated, but on the other hand it can be used to send mail to the outside (unlike incoming port 25 which doesn’t, as that would make it an open relay)). Both the users’ authentication that would otherwise be in clear text, and the fact that only a limited number of users (your users, too) will be accessing that port through email clients (never other email servers that you don’t control) make forcing encryption here a no-brainer.

NOTE: naturally, this is not an exhaustive Postfix tutorial. I’ll be assuming you already have a working server, and just want to add TLS encryption to it. Also, some parts may not apply to your case.

1. Have/get a TLS certificate for your email host’s public name

If you don’t already have one, see how to create a new TLS certificate. We’ll be using an RSA certificate here (instead of going with ECDSA) since unlike, say, a web server accessed by standard browsers, you can’t control what other email servers support 3, therefore it’s best to choose the most common option. 4

Note that the certificate’s CN must match your email host’s public name (e.g. “mail.domain.com“). That will also probably correspond to (one of) the domain’s MX records.

2. Postfix configuration

Again, I’ll be assuming your non-TLS Postfix is already working fine.

Put your certificate and key on /etc/postfix (for instance). For this example, I’ll be calling them myserver-full.crt and myserver.key.

In /etc/postfix/main.cf, add the following lines:

# TLS configuration starts here

tls_random_source = dev:/dev/urandom

# openssl_path=/usr/local/libressl/bin/openssl
# uncomment and edit the above if you're using a different "openssl" than the system's
# (in this case, LibreSSL)

# SMTP from your server to others
smtp_tls_key_file = /etc/postfix/myserver.key
smtp_tls_cert_file = /etc/postfix/myserver-full.crt
smtp_tls_CAfile = /etc/postfix/myserver-full.crt
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtp_tls_mandatory_protocols=!SSLv2,!SSLv3
smtp_tls_loglevel = 1
smtp_tls_session_cache_database =
    btree:/var/lib/postfix/smtp_tls_session_cache

# SMTP from other servers to yours
smtpd_tls_key_file = /etc/postfix/myserver.key
smtpd_tls_cert_file = /etc/postfix/myserver-full.crt
smtpd_tls_CAfile = /etc/postfix/myserver-full.crt
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
smtpd_tls_loglevel = 1
smtpd_tls_session_cache_database =
    btree:/var/lib/postfix/smtpd_tls_session_cache

# TLS configuration ends here

And in /etc/postfix/master.cf, assuming you already have a “submission” section a bit like this:

submission  inet  n     -       n       -       -       smtpd
    -o smtpd_etrn_restrictions=reject
    -o smtpd_sasl_auth_enable=yes
    -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
    -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
    -o smtpd_helo_restrictions=permit_mynetworks,permit

Add to that section:

  -o smtpd_tls_security_level=encrypt

The above forces encryption for the submission service (remember that it’s not required for normal SMTP, it’s just desired).

In short:

  • your server tries to connect to others using TLS, but falls back to an unencrypted connection if the other side doesn’t support encryption;
  • other servers can connect to yours using TLS, but if they don’t attempt it then the connection will be unencrypted;
  • your users *must* use encryption (and authentication) to send mail through your server.

Restart your server, and check the logs: you should be getting mentions of TLS now. For instance, an email server starting an encrypted connection to yours looks like:

Sep 6 14:25:58 drax postfix/smtpd[22727]: Anonymous TLS connection established from lists.openbsd.org[192.43.244.163]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)

Questions? Suggestions?

(And what about securing mailbox access? Enjoy: How to configure TLS encryption in Dovecot)

Ubuntu/Debian: Installing Nginx/Postfix/Dovecot using OpenSSL 1.1.x

OpenSSLSo, let’s say you have a Ubuntu or Debian server, using one or more of Nginx, Postfix, and Dovecot, and you’d like to have them link to OpenSSL 1.1.x instead of the default OpenSSL (as of Ubuntu 17.04, it’s version 1.0.2g). (Reasons may include wanting to use modern ciphers such as ChaCha20, or trying out support for the most recent TLS 1.3 draft. Also, if you want to try out LibreSSL instead of OpenSSL 1.1, please check out the previous post.)

So, here’s a relatively simple way, that doesn’t change the system’s default OpenSSL (believe me, that wouldn’t be a good idea, unless you recompiled everything):

Install dependencies:

apt-get install build-essential
apt-get build-dep openssl nginx dovecot postfix

Install OpenSSL 1.1.x:

  • download the latest 1.1.x source from www.openssl.org
  • compile and install it with:
./config --prefix=/usr/local/openssl11 --openssldir=/usr/local/openssl11 && make && make install

Install Nginx:

rm -rf /usr/local/src/nginx-openssl11
mkdir /usr/local/src/nginx-openssl11
cd /usr/local/src/nginx-openssl11
apt-get source nginx # ignore the permissions error at the end
  • edit nginx-<version>/debian/rules:  add

–with-openssl=/usr/local/src/openssl-

to the beginning of the common_configure_flags option (note that that’s the source directory you used to compile OpenSSL 1.1.x, not where you installed it to);

debuild -uc -us -b
  • install the required packages in the parent directory with “dpkg -i ” (do dpkg -l | grep nginx to see which you have installed; typically you’ll want to install the newly created versions of those);
apt-mark hold nginx* # to prevent nginx from being updated from Ubuntu / Debian updates

Done! You can now play around with the Mozilla TLS Guide to add support for modern ciphers to your Nginx’s configuration, and use SSLLabs’s SSL Server Test tool to check if they are correctly enabled.

Install Postfix:

It’s just like Nginx (replacing “nginx” with “postfix” in every command / directory name, of course), except that the changes to debian/rules are these:

  • find -DHAS_SSL, add -I/usr/include/openssl11/include/openssl in front of it;
  • find AUXLIBS += , add -L/usr/local/openssl11/lib in front of it
  • find the line with dh_shlibdeps -a, add –dpkg-shlibdeps-params=–ignore-missing-info to it
  • don’t forget the apt-mark hold postfix* at the end.

Install Dovecot:

Again, use the Nginx instructions, using “dovecot” instead of “nginx” everywhere, except that the changes to debian/rules should be:

  • after the line:
export DEB_BUILD_MAINT_OPTIONS=hardening=+all

add:

export SSL_CFLAGS=-I/usr/local/openssl11/include
export SSL_LIBS=-L/usr/local/openssl11/lib -lssl -lcrypto
  • after the section:
override_dh_makeshlibs:
# Do not add an ldconfig trigger; none of the dovecot shared libraries
# are public.
        dh_makeshlibs -n

add:

override_dh_shlibdeps:
        dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info

NOTE: the indentation in the second line needs to be a tab, don’t use spaces.

Again, remember to apt-mark hold dovecot* after installation.

How to check if your new installations of Postfix and/or Dovecot are using OpenSSL 1.1.x instead of the default OpenSSL 1.0.x? You could use ldd to check what SSL / TLS libraries your binaries and/or libraries link to, but the best way is probably to use a tool such as sslscan, which you can use to check what ciphers your SMTP, IMAP, etc. support (including with STARTTLS). If you see ChaCha20 in there, everything is fine. 🙂

If you ever want to go back to “normal” versions of these servers, just do apt-mark unhold nginx* (for instance).

Ubuntu/Debian: Installing Nginx/Postfix/Dovecot using LibreSSL

LibreSSLSo, let’s say you have a Ubuntu or Debian server, using one or more of Nginx, Postfix, and Dovecot, and you’d like to have them link to LibreSSL instead of the default OpenSSL. (I won’t go much into the possible reasons for it; maybe you’re bothered because modern distros are still sticking with OpenSSL 1.0.x, which is ancient and doesn’t support modern ciphers such as ChaCha20, or you trust the OpenBSD developers more than you trust the OpenSSL ones, or — and there’s nothing wrong with that — you want to do it just for fun. You could also use OpenSSL 1.1.x — check out this (very similar) post.)

So, here’s a relatively simple way, that doesn’t change the system’s default OpenSSL (believe me, that wouldn’t be a good idea, unless you recompiled everything):

Install dependencies:

apt-get install build-essential
apt-get build-dep openssl nginx dovecot postfix

Install LibreSSL:

  • download the latest portable source from www.libressl.org
  • compile and install it with:
./configure --prefix=/usr/local/libressl --with-openssldir=/usr/local/libressl && make && make install

Install Nginx:

rm -rf /usr/local/src/nginx-libressl
mkdir /usr/local/src/nginx-libressl
cd /usr/local/src/nginx-libressl
apt-get source nginx # ignore the permissions error at the end
  • edit the file nginx-<versão>/debian/rules:  add
-I/usr/local/libressl/include

to the line beginning with “debian_cflags:=“, and:

-L/usr/local/libressl/lib

to the line that begins with “debian_ldflags:“. After that, enter the nginx-<version> directory and compile the packages with the command:

debuild -uc -us -b
  • install the required packages in the parent directory with “dpkg -i <package names>” (do “dpkg -l | grep nginx” to see which you have installed; typically you’ll want to install the newly created versions of those);
apt-mark hold nginx* # to prevent nginx from being updated from Ubuntu / Debian updates

Done! You can now play around with the Mozilla TLS Guide to add support for modern ciphers to your Nginx’s configuration, and use SSL Labs’s SSL Server Test tool to check if they are correctly enabled.

Install Postfix:

It’s just like Nginx (replacing “nginx” with “postfix” in every command / directory name, of course), except that the changes to debian/rules are these:

  • find -DHAS_SSL, add -I/usr/include/libressl/include/openssl in front of it;
  • find AUXLIBS += , add -L/usr/local/libressl/lib in front of it
  • find the line with dh_shlibdeps -a, add –dpkg-shlibdeps-params=–ignore-missing-info to it
  • don’t forget the apt-mark hold postfix* at the end.

Install Dovecot:

Again, use the Nginx instructions, using “dovecot” instead of “nginx” everywhere, except that the changes to debian/rules should be:

  • after the line:
export DEB_BUILD_MAINT_OPTIONS=hardening=+all

add:

export SSL_CFLAGS=-I/usr/local/libressl/include
export SSL_LIBS=-L/usr/local/libressl/lib -lssl -lcrypto
  • after the section:
override_dh_makeshlibs:
# Do not add an ldconfig trigger; none of the dovecot shared libraries
# are public.
        dh_makeshlibs -n

add:

override_dh_shlibdeps:
        dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info

NOTE: the indentation in the second line needs to be a tab, don’t use spaces.

Again, remember to apt-mark hold dovecot* after installation.

How to check if your new installations of Postfix and/or Dovecot are using LibreSSL instead of the default OpenSSL? You could use ldd to check what SSL / TLS libraries your binaries and/or libraries link to, but the best way is probably to use a tool such as sslscan, which you can use to check what ciphers your SMTP, IMAP, etc. support (including with STARTTLS). If you see ChaCha20 in there, everything is fine. 🙂

If you ever want to go back to “normal” versions of these servers, just do apt-mark unhold nginx* (for instance).

I’ve also added /usr/local/libressl/bin to the beginning of my PATH environment variable, so that the LibreSSL binaries are used by default (e.g. to generate keys, CSRs, etc.), although this isn’t necessary for Nginx, etc. to work.