Skip to content

Instantly share code, notes, and snippets.

@bryanforbes
Last active August 31, 2022 19:26
Show Gist options
  • Save bryanforbes/36968612b4705409a434 to your computer and use it in GitHub Desktop.
Save bryanforbes/36968612b4705409a434 to your computer and use it in GitHub Desktop.

Postfix Hardening for MX Guarddog

For this guide, the following assumptions are made:

  • MX Guarddog is being used to filter spam
  • MX records have been set to MX Guarddog's servers
  • Postfix successfully receives and delivers mail from MX Guarddog's servers

After following the instructions at MX Guarddog for setting up the MX records, email addresses, email server, and quarantine reports, everything should Just Work™. Spam should slow to a trickle; MX Guarddog does a really good job of spam filtering! The only thing they can't protect against is spammers that ignore the MX records of a server and try to connect directly to the server. For that, some configuration will need to be done. In addition to this, we'll also prevent submitting email on port 25 and only allow it on the submission port (587).

To restrict the clients which can connect to the SMTP port (25), you'll need the following configuration parameters in /etc/postfix/main.cf:

/etc/postfix/main.cf

# TLS parameters
smtpd_tls_cert_file=/path/to/ssl.crt
smtpd_tls_key_file=/path/to/ssl.key
smtpd_use_tls=yes
smtpd_tls_auth_only=yes

# Enabling SMTP for authenticated users, and handing off authentication to Dovecot
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
# smtpd_sasl_auth_enable = yes

## Uncomment the following configuration to keep rejected mails queued instead of returning them to the sender. This
## is good for when you're initially turning this feature on and want to be sure you're not rejecting the wrong email.
# soft_bounce = yes
smtpd_recipient_restrictions =
	reject_unauth_destination,
	check_client_access hash:/etc/postfix/client_access,
	reject

I placed all of this right before the myhostname parameter. The first thing I did was enable TLS (smtpd_use_tls) and make STARTTLS required for authentication (smtpd_tls_auth_only). I'm using Dovecot for IMAP, so I configured Postfix to use Dovecot for user authentication. You'll also notice that I have the parameter to enable SASL authentication (smtpd_sasl_auth_enable) commented out (the default value is no). This has the effect of disallowing submission on port 25 since users aren't able to authenticate on that port. The smtpd_recipient_restrictions parameter does a few things:

  • reject_unauth_destination rejects unless the email's receipt can be relayed or this server is the final destination
  • check_client_access checks the connecting client against a known list of IP addresses and performs the action specified
  • reject will reject any email that doesn't match either of the other two restrictions

The next step was to create /etc/postfix/client_access with MX Guarddog's IP addresses:

/etc/postfix/client_access

# MX Guarddog's FQDN with all of its delivery server IP addresses
64.38.239.82		OK
64.38.239.83		OK
122.103.250.12		OK
64.38.239.86		OK
66.85.178.50		OK
85.195.83.79		OK
64.38.239.84		OK
64.38.239.85		OK

This file has to have postmap run on it to turn it into a BerkelyDB file:

$ sudo postmap /etc/postfix/client_access

Postfix now is set up to only accept delivery on the SMTP port and reject submissions. In order to accept submissions on port 587, we'll need to edit /etc/postfix/master.cf. Most distributions will have a default /etc/postfix/master.cf with some lines commented out. Make sure to uncomment the line starting with submission and comment out the line starting with smtps. A lot of guides will tell you to enable that port because it is the official STARTTLS port, however the SMTPS port was never official and most clients can do STARTTLS over the submission port. I've added the configuration parameters for the submission port on the lines following the submission line:

/etc/postfix/master.cf

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       -       -       -       smtpd
submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_mynetworks,reject_non_fqdn_recipient,permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
#smtps     inet  n       -       -       -       -       smtpd

Here's what I've done:

  • Made it easier to differentiate between the SMTP port and the submission port in my logs (syslog_name=postfix/submission)
  • Required STARTTLS (smtpd_tls_security_level=encrypt)
  • Enabled authentication (smtpd_sasl_auth_enable=yes)
  • Permit sending from localhost and sending by authenticated users (smtpd_recipient_restrictions=permit_mynetworks,reject_non_fqdn_recipient,permit_sasl_authenticated,reject)
  • Added an identifier for mail filter programs such as Amavis (milter_macro_daemon_name=ORIGINATING)

Finally, restart postfix:

$ sudo service postfix restart

These settings will take the spam you receive from a trickle to a drip.

Automate Updating Client Access DB

Hat tip to @jwr456 and @JBES for their initial ideas for the script

  • Create /etc/postfix/update-client-access with the following contents:

    #!/bin/bash
    
    set -e
    
    MXGD_IPS="$(/usr/bin/dig +short servers.ik2.com)"
    CONTENTS=""
    
    for ip in $MXGD_IPS; do
        CONTENTS="$CONTENTS$ip\tOK\n"
    done
    
    echo -e $CONTENTS > /tmp/client_access.in
    /usr/sbin/postmap /tmp/client_access.in
    mv /tmp/client_access.in /etc/postfix
    mv /tmp/client_access.in.db /etc/postfix/client_access.db

    NOTE There is no need to reload postfix according to the docs:

    If you change a local file based database such as DBM or Berkeley DB, there is no need to execute "postfix reload". Postfix uses file locking to avoid read/write access conflicts, and whenever a Postfix daemon process notices that a file has changed it will terminate before handling the next client request, so that a new process can initialize with the new database.

  • Run the following commands:

    sudo chmod 0700 /etc/postfix/update-client-access
    sudo /etc/postfix/update-client-access
  • Create /etc/systemd/system/postfix-update-client-access.service with the following contents:

    [Unit]
    Description=Update the Postfix client access database with MX Guarddog's IP addresses
    After=network-online.target
    
    [Service]
    Type=oneshot
    ExecStart=/etc/postfix/update-client-access
    ProtectHome=true
    ProtectSystem=true
    PrivateTmp=true
  • Run sudo systemctl start postfix-update-client-access.service and ensure that /etc/postfix/client_access.in and /etc/postfix/client_access.db are updated

  • Create /etc/systemd/system/postfix-update-client-access.timer with the following contents:

    [Unit]
    Description=Monthly renewal of MX Guarddog's IP addresses
    
    [Timer]
    OnCalendar=monthly
    RandomizedDelaySec=1h
    Persistent=true
    
    [Install]
    WantedBy=timers.target
  • Enable the monthly timer by running the following:

    sudo systemctl daemon-reload
    sudo systemctl enable postfix-update-client-access.timer
    sudo systemctl start postfix-update-client-access.timer

Creative Commons License
Postfix Hardening for MX Guarddog by Bryan Forbes is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

@jwr456
Copy link

jwr456 commented Mar 23, 2015

Create a cron job to update /etc/postfix/client_access based on the answer section of dig -q servers.ik2.com and update the BerkleyDB file with postmap:

!/bin/bash

for x in /usr/bin/dig +short servers.ik2.com; do echo "$x OK"; done > /etc/postfix/client_access

/usr/sbin/postmap /etc/postfix/client_access

/etc/init.d/postfix reload

@JBES
Copy link

JBES commented Aug 13, 2016

Bash script requires quotes to work successfully:
Ubuntu 16.04

#!/bin/bash
for x in `/usr/bin/dig +short servers.ik2.com`; do echo "$x OK"; done > /etc/postfix/client_access
/usr/sbin/postmap /etc/postfix/client_access
service postfix restart

@arkestlerdev
Copy link

I realize this in an old article, however, if I follow this, then when I try to send a test email from MX Guard Dog, I get:
554 5.7.1 recipient address rejected access denied

Any suggestions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment