Require TLS for certain domains in Postfix

Recently, I was asked if it was possible to require Transport Layer Security (TLS) for sending email for certain domains, through a specific relay. For added complexity, the list of domains would need to be updated daily. Of course it’s possible, I said. And yes, Postfix is up to the challenge. Here’s how to require TLS for certain domains in Postfix.

The following set-up is on Ubuntu 14.04 but it should work on any mail server that uses Postfix.

Requirements

  1. A working mail server
  2. A certificate used to authenticate the server (self-signed is allowed)
  3. A list of domains that you want to enforce TLS for

Step 1, how to get a mail server with Postfix up and running, is beyond the scope of this document. In the case of Ubuntu, I refer you to the official documentation. For other distributions, please consult their specific documentation.

Step 2, how to get a certificate, is outlined below, as is step 3 and beyond.

Self-signed certificate and set up Postfix for TLS

I should point out at this moment, that a self-signed certificate is not as good as a certificate from an official certificate authority. A self-signed certificate will ensure working encryption but it is not trustworthy to third parties when it comes to verification of the identity of your mail server. If the latter is important for your intended use, please use a certificate from a proper certificate authority instead. Installation is much the same as that of a self-signed certificate.

To generate a self-signed certificate for TLS on Ubuntu, we need to go through the following steps. First, we need to generate the keys for a Certificate Signing Request (CSR). The current standard is to use at least 2048 bits. I’m going to use the directory /etc/ssl/local as a temporary location to store stuff. You will have to create it manually before proceeding (hint: mkdir). Do the following at the command prompt:

# openssl genrsa -des3 -out /etc/ssl/local/server.key 2048
Generating RSA private key, 2048 bit long modulus
......................................................+++
......................+++
e is 65537 (0x10001)
Enter pass phrase for /etc/ssl/local/server.key:

Choose a pass phrase and press enter. It is an inconvenience to have certificates used by services such a Postfix or apache prompt for pass phrases on boot, so it is acceptable to use certificates without a pass phrase for them. We’ll create an insecure key without a pass phrase and move the secure server key out of the way next.

# openssl rsa -in /etc/ssl/local/server.key -out /etc/ssl/local/server.key.insecure
# mv /etc/ssl/local/server.key /etc/ssl/local/server.key.secure
# mv /etc/ssl/local/server.key.insecure /etc/ssl/local/server.key

Now that the insecure server key is knows as server.key, we can generate the CSR.

# openssl req -new -key /etc/ssl/local/server.key -out /etc/ssl/local/server.csr

The above command will prompt you for information that should be contained in the certificate. Be as complete as you can. When you are done, the CSR will be generated. You can use this CSR to submit to a Certificate Authority for a proper certificate or you can use it to generate a self-signed certificate. We’ll proceed with that last option.

# openssl x509 -req -days 1095 -in /etc/ssl/local/server.csr -signkey /etc/ssl/local/server.key -out /etc/ssl/local/server.crt

The above command will generate a certificate that is valid for 3 years (1095 days), filled with the information that you have entered when generating the CSR. Now we have the certificate, we’re going to move it to the proper location.

# cp /etc/ssl/local/server.crt /etc/ssl/certs
# cp /etc/ssl/local/server.key /etc/ssl/private

Next, we’ll tell Postfix to use this certificate to set up TLS connections. This is easily done via the postconf command. We’re also going to take this opportunity to enable TLS as an option for all connections. Enter the following line by line, followed by an enter at the end of each line.

sudo postconf -e 'smtp_tls_security_level = may'
sudo postconf -e 'smtpd_tls_security_level = may'
sudo postconf -e 'smtp_tls_note_starttls_offer = yes'
sudo postconf -e 'smtpd_tls_key_file = /etc/ssl/private/server.key'
sudo postconf -e 'smtpd_tls_cert_file = /etc/ssl/certs/server.crt'
sudo postconf -e 'smtpd_tls_loglevel = 1'
sudo postconf -e 'smtpd_tls_received_header = yes'

With these lines in place, all we need to do to activate our changes, is to restart Postfix.

sudo service Postfix restart

This completes step 2.

Forcing TLS for specific domains

Right now, we have Postfix working and using TLS for domains that request it. However, the objective is to have Postfix use TLS as a requirement for a list of domains. Requiring Postfix to use TLS is not difficult, you can set the parameter ‘smtp_tls_security_level = encrypt’ in main.cf. This will force encryption on all connections but since many mail servers do not use TLS, this will cause problems for you. Taking a look at the Postfix documentation, I came across the option to create a TLS policy table. This table allows you to set TLS security options on a per domain basis. My goal is to include all domains that our policy requires TLS for, and set them to encrypt.

Since the list of domains would be updated frequently, it is made available online. My plan is to use a script to download the list once per day and use it to create a TLS policy table. This will require setting each domain to “encrypt” by hand and then running the postmap command against the list to create a hash table that Postfix can use. Here is what the script will have to accomplish:

  1. Download updated domain list using curl
  2. Add the word “encrypt” to each line, preceded by a whitespace using the sed command
  3. Run the postmap command on the list to create the hash db

Here is what my script looks like, hiding any specific info:

#!/bin/bash
#
# Download latest domainbook from https://example.com/domainbook.txt
curl https://example.com/domainbook.txt > /etc/Postfix/require-tls-policy 

# Wait for the above to finish
sleep 10

# Add the option "encrypt" after each line in the downloaded file
# This is required by postmap to create the table
sed -e 's/$/ encrypt/' -i /etc/Postfix/require-tls-policy

# Now do a postmap of the downloaded file
postmap /etc/Postfix/require-tls-policy

# Wait a bit more
sleep 10

exit 0

This does the trick nicely, creating a /etc/Postfix/require-tls-policy.db file. You can automate running this script as often as needed via crontab.
Next, we need to change the Postfix configuration to use this policy table. We can do that in /etc/Postfix/main.cf, either directly or using the postconf command we used earlier. Make the following line gets added:

smtp_tls_policy_maps = hash:/etc/Postfix/require-tls-policy

There’s no need to add the extension (.db). Also, make sure the name of the policy map, matches the name of your file, otherwise Postfix isn’t going to find it! Send a test email to the other side and see if it uses encryption now (it should!). To see the details, you can include the following statement in your /etc/Postfix/main.cf.

debug_peer_list = example.com

This will log technical details about the communication with this domain in your mail server logs. To see if TLS is indeed used, look for lines like the ones below.

Jul  6 15:42:01 mail Postfix/smtp[3772]: < example.com[x.x.x.x]:25: 220 2.0.0 Ready to start TLS

This tells you that TLS is indeed used.

Forcing a specific relay for specified domains

Now that we have Postfix set up to use TLS for all domains in our domain list, we need to complete one more step and that is to force a delivery to a specific MTA for all domains in this list. For this, Postfix uses a transport table, which can also be generated for by the postmap command we’ve already seen. Since out site already has a transport map, I needed to figure out a way to keep this file dynamic while retaining our static transport map.

The solution I came up with was simple but seems effective so far. I updated the script from earlier to build the transport map from the downloaded domain list, appending our static transport map at the bottom and then building the transport map using postmap. Here is the completed script below:

#!/bin/bash
#
# Download latest domainbook from https://example.com/domainbook.txt
# Convert it to a Postfix hash table
curl https://example.com/domainbook.txt > /etc/Postfix/domainbook 

# Wait for the above to finish
sleep 10

# copy domainbook to require-tls-policy file 
cp /etc/Postfix/domainbook /etc/Postfix/require-tls-policy

# Add the option "encrypt" after each line in the downloaded file
# This is required by postmap
sed -e 's/$/ encrypt/' -i /etc/Postfix/require-tls-policy

# Now do a postmap of the downloaded file
postmap /etc/Postfix/require-tls-policy

# Wait a bit more
sleep 10

# Next, build the new transpost table using domainbook and static transport table
cp /etc/Postfix/domainbook /etc/Postfix/transport

# Add the xxxxmail relay at the end of each line in transport table
sed -e 's/$/ smtp:[x.x.x.x]/' -i /etc/Postfix/transport
# Append static transport map to end of file
cat /etc/Postfix/transport-static >> /etc/Postfix/transport
# Build the new transport table
postmap /etc/Postfix/transport

exit 0

Yes, I should probably add some error checking…

Any feedback, tips or errors are welcome!

2 Replies to “Require TLS for certain domains in Postfix”

  1. Why would you disable selection/copy and paste when you write a tutorial with a bunch of commands and code?

    1. I understand your annoyance but there are two good reasons for that.

      1. I’ve had people copy entire swathes of text from my blog and use it as their own. I don’t mind the copying but at least give me credit for writing it.
      2. Worse though, there are serious security implications attached to simply copying and pasting commands into your terminal! You should never be doing that. Have a look at this article to see why I say that: https://security.stackexchange.com/questions/113627/what-is-the-risk-of-copy-and-pasting-linux-commands-from-a-website-how-can-some

      Hope that explains it!

Leave a Reply

Your email address will not be published. Required fields are marked *