Last Updated on 2022-07-08 by Joop Beris
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.
- A working mail server
- A certificate used to authenticate the server (self-signed is allowed)
- 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:
- Download updated domain list using curl
- Add the word “encrypt” to each line, preceded by a whitespace using the sed command
- 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: < 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!