Eliminate spam using SSL with an open source certification authority

Use a Let’s Encrypt certificate with MailCleaner for STARTTLS and SSL. Here's how.
75 readers like this.
Chat via email

MailCleaner is a feature-rich, open source antispam solution. Its virtual appliances (VMs) available for distribution come out-of-the-box with self-signed certificates for both the web interface and the MTA services.

This requires you to supply your own valid, publicly trusted certificate. Using a Let's Encrypt certificate is a great way to accomplish that because it's free, safe, and automated.

When requesting a Let's Encrypt certificate, the most important step is the hostname validation. If you don't know about it, consult the documentation.

Firewall requirements

First of all, you need to define which hostnames you will use, including your MX records, and they must point to the IP address you're using to publish your MailCleaner server.

If you choose to perform the validation using local port 80 on your MailCleaner box, you will have to include a few commands to temporarily stop the Apache service during the certificate request. That's why I recommend using an alternative port, which, in our examples, will be port TCP 8090.

You have a few options in this scenario:

Option 1: Create rules in your reverse proxy to forward Let's Encrypt validation requests to your MailCleaner server. You have to redirect every request sent to port TCP 80, whose destination hostname is your MailCleaner external FQDN, and the path starts with /.well-known/acme-challenge/ to port TCP 8090 on your MailCleaner server.

Option 2: Using a NAT rule, for example, redirect the traffic sent to port TCP 80 to port TCP 8090 on your MailCleaner server.

Option 3: Redirect/allow traffic sent to port TCP 80 to the actual port TCP 80 on your MailCleaner server, which is less secure, less flexible, and not recommended.

Alternatively, you could have the certificate request and the name validation performed somewhere else (like in your firewall) and create a routine for copying the cert files to your MailCleaner box. If you have a pfSense firewall with the ACME package, for example, you can try to merge the concepts within this article with this how-to.

Installing Certbot

Certbot is an open source tool for requesting and managing Let's Encrypt certificates.

To install Certbot on your MailCleaner server, log in as root (in the console or through SSH) and run:

$ wget https://dl.eff.org/certbot-auto
$ mv certbot-auto /usr/local/bin/certbot-auto
$ chown root /usr/local/bin/certbot-auto
$ chmod 0755 /usr/local/bin/certbot-auto

Testing certificate name validation

If you're using an alternate port, you need to open it in the local firewall on your MailCleaner server:

iptables -A INPUT -p tcp -m tcp --dport 8090 -j ACCEPT

Note: MailCleaner keeps local firewall rules in its database and sets the iptables config every time the server loads. It's imperative that you add port 8090 to the firewall table inside MailCleaner's MySQL database; otherwise, every renewal process will fail. To learn how to do this, take a look at the section titled "Accessing MailCleaner's MySQL database" in the article How to install MailCleaner 2020.01.

Now, let's try to issue our certificate using Let's Encrypt's staging (testing) server. Please replace the appropriate values with your email address and your MailCleaner server hostname(s).

Option 1: If you are using the alternative port 8090, use this command line:

$ certbot-auto certonly --standalone --preferred-challenges http \
--http-01-port 8090 --email myemail@domain.com --no-eff-email \
--agree-tos --staging -d myhostname.mydomain.com

If you have more than one name, just add them with "-d" at the end:

-d mx1.mydomain.com \
-d mx2.mydomain.com \
-d spam.mydomain.com

Option 2: If you are using local port 80, use this command line:

$ certbot-auto certonly --standalone --preferred-challenges http \
 --email myemail@domain.com --no-eff-email --agree-tos --staging \
-d myhostname.mydomain.com \
--pre-hook "/usr/mailcleaner/etc/init.d/apache stop" \
--post-hook "/usr/mailcleaner/etc/init.d/apache start"

Note: After issuing this command, you will hit a bootstrapping routine identifying missing dependencies, mostly Python packages. Let it install the necessary software.

If everything went fine, you should see a result like this:

root#mailcleaner:~#
root@mailcleaner:~# certbot-auto certonly \
--standalone --preferred-challenges http \
--http-01-port 8090 --email victor@domain.com \
--no-eff-email --agree-tos --staging \
-d mail.example.com

Saving debug log to /var/log/letsencrypt/ 
Plugins selected: Authenticator standalone
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for mail.example.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
Your certificate and chain have been saved at:
/etc/letsencrypt/live/mail.example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/ live/mail.example.com/privkey.pem
[...]
root@mailcleaner:~# 

If it didn't go well, keep in mind that most errors with this process are caused by Let's Encrypt servers not being able to reach your server. Check if your firewall configuration is really OK.

Request your certificate

When the certificate issuing process is working correctly with the staging server, go ahead and request your certificate for production (removing the staging parameter):

certbot-auto certonly --standalone --preferred-challenges http --http-01-port 8090 --email myemail@domain.com --no-eff-email --agree-tos --force-renewal -d myhostname.mydomain.com

Note: Adapt the command line if you're not using the alternative port 8090. If that's the case, don't forget the pre-hook and post-hook.

The result screen is pretty similar. You will now have a valid certificate at the following path:

/etc/letsencrypt/live/my__hostname_.yourdomain.com_/
root#mailcleaner:~# ls /etc/letsencrypt/live/mail.example.com
cert.pem chain.pem fullchain.pem privkey.pem README
root@mailcleaner:~# 

Automate certificate assignment and renewal

The last piece of this puzzle is the great script provided by "GRahamJB" in this MailCleaner forum topic. You can download the script from here.

Let's save this script in our server. Create the following file:

$ nano /usr/local/bin/set-certificates.pl

Then paste the contents of the script and save it (Ctrl + X). And give the script permission to run:

$ chmod +x /usr/local/bin/set-certificates.pl

Now run the script to assign your certificate to the web interface and the MTA services:

root@mailcleaner:~# set-certificates.pl --set_web \ 
--set_mta_in --set_mta_out \
--key /etc/letsencrypt/live/mail.example.com/privkey.pem \
--data /etc/letsencrypt/live/mail.example.com/cert.pem \
--chain /etc/letsencrypt/live/mail.example.com/chain.pem

Stopping Apache: stopped.
Starting Apache: started.
Stopping Exim stage 1: stopped.
Starting Exim stage 1: started.
Stopping Exim stage 4: stopped.
Starting Exim stage 4: started.
root@mailcleaner:~# 

Now that we know it works, schedule these commands to run weekly, using cron and Certbot's built-in renewal routine:

$ nano /etc/letsencrypt/renewal/yourhostname.yourdomain.com.conf

Check if the options look correct and add the following line at the end (the same set-certificates.pl you just ran, preceded by renew_hook =):

.
.
# Options used in the renewal process
[renewalparams]
authenticator = standalone
account = 9d670ed7c63c6238f90f042f852fc33e
pref_challs = http-01,
http01_port = 8090
server = https://acme-v02.api.letsencrypt.org/directory
# Set MailCleaner certs
renew_hook = set-certificates.pl --set_web --set_mta_in --set_mta_out --key /etc/letsencrypt/live/myhostname.mydomain.com/privkey.pem --data /etc/letsencrypt/live/myhostname.mydomain.com/cert.pem --chain /etc/letsencrypt/live/myhostname.mydomain.com/chain.pem

Note that the "renew_hook = set-cert…" command must be one single line. Save the file and run the following command to test it:

$ certbot-auto renew --force-renewal

If the renewal succeeds, you'll see a result similar to the one below. Note how our renew_hook command was called. The certificate has been updated in MailCleaner and the necessary services restarted.

root@mailcleaner:~# certbot-auto renew --force-reneval
Saving debug log to /var/log/letsencrypt/letsencrypt.log

Processing /etc/ letsencrypt/renewal/mail.example.com.conf
Plugins selected: Authenticator standalone, Installer None
Renewing an existing certificate
Running deploy-hook command: set-certificates.pl \
--set_web --set_mta_in --set_mta_out \
--key /etc/letsencrypt/live/mail.example.com/privkey.pem \
--data /etc/letsencrypt/live/mail.example.com/cert.pem \
--chain /etc/letsencrypt/live/mail.example.com/chain.pem
Output from deploy-hook conmtwand set-certificates.pl:

Stopping Apache: stopped.
Starting Apache: started.
Stopping Exim stage 1: stopped.
Starting Exim stage 1: started.
Stopping Exim stage 4: stopped.
Starting Exim stage 4: started.

new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/mail.example.com/fullchain.pem

Congratulations, all renewals succeeded. 
The following certs have been renewed:
/etc/letsencrypt/live/mail.example.com/fullchain.pem (success)
root@mailcleaner:~# 

Now, let's add that renew command to cron:

$ crontab -e

Add the following line and save the file. This will make Certbot run every Sunday at 2:00am:

0 2 * * 7 /usr/local/bin/certbot-auto renew

If crontab doesn't open the way you expect, run select-editor to choose the editor you like (nano, for example). If you want to check the result, run crontab -l.

By default, Certbot will only renew the certificate if it has less than 30 days left before its expiry date. If the cert is not due to expire, Certbot will not renew it (nor call hooks, of course).

Testing results

If you access MailCleaner's web interface, you'll see that the SSL certificate is valid. And if you run the following command in your server, you can see that the certificate being presented on STARTTLS is the new Let's Encrypt cert you just set:

$ openssl s_client -connect localhost:25 -starttls smtp

 

What to read next
Here's a photo of Victor Lopes
Systems administrator. MCSA, Security+. Open source fan. Development enthusiast. NSSP program at UBC, Canada.

2 Comments

Nice product but is based on Debian 8

The current virtual appliance (the VMs available for distribution) is Debian 8, but the packages are all up-to-date and the codebase is in active development. The code is probably compatible with Debian 9 or 10 already, and now that Debian 8 is in End-of-Life, I believe they will release new versions of these VMs soon.

In reply to by bart

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.