The issues below took me way to long to find a solution for, so I hope it will help others.
By default, a rootless docker instance doesn't allow passing the real IP's to the docker containers, instead the IP of network bridge is communicated. In case of docker this is 172.22.1.1
. This is annoying in NGINX because you don't know where your visitors came from, but dangerous in mailcow installations. Because of this, all connections to Postfix will be considered as coming from the internal mailcow network and thus considered safe. The Postfix installation has now become an openrelay. This means that everyone connecting to port 25 can send emails through your mailcow instance. Which spammers will very soon do.
The only solution for this (as well for NGINX) is putting Postfix behind a HAProxy installation. HAProxy can add a header to each TCP-package in which the real IP is encapsulated. Postfix can be configured to read this header so it still knows the real IP a connection came from.
Simply put this in the HAProxy configuration:
frontend mail_smtp
bind *:25
mode tcp
option tcplog
default_backend backend_postfix_servers
backend backend_postfix_servers
mode tcp
server server1 127.0.0.1:1025 send-proxy check
In mailcow.conf
, change the SMTP_PORT
to 1025
.
And add these lines in the data/conf/postfix/extra.cf
:
postscreen_upstream_proxy_protocol = haproxy
postscreen_upstream_proxy_timeout = 5s
Restart HAProxy on the host and mailcow as the docker user and you're fine again. A simple double check that HAProxy is actually connected to the Postfix instance can be done by following the logs.
Inside the mailcow-dockerized root folder run: docker compose logs -f postfix-mailcow
Each 2 seconds, you'll see this heartbeat message coming from HAProxy:
postfix-mailcow-1 | Apr 15 23:21:41 ea123f1cde4e postfix/postscreen[378]: CONNECT from [127.0.0.1]:48268 to [127.0.0.1]:1025
postfix-mailcow-1 | Apr 15 23:21:41 ea123f1cde4e postfix/postscreen[378]: ALLOWLISTED [127.0.0.1]:48268
postfix-mailcow-1 | Apr 15 23:21:41 ea123f1cde4e postfix/smtpd[19012]: connect from localhost[127.0.0.1]
postfix-mailcow-1 | Apr 15 23:21:41 ea123f1cde4e postfix/smtpd[19012]: lost connection after CONNECT from localhost[127.0.0.1]
postfix-mailcow-1 | Apr 15 23:21:41 ea123f1cde4e postfix/smtpd[19012]: disconnect from localhost[127.0.0.1] commands=0/0
When installing a default rootless docker instance on Debian, it's missing the slirp4netns
network driver. That driver can be installed with the docker-ce-rootless-extras
package. The default driver installed doesn't allow any container to ping to the outside world. Simply, installing that package and restarting docker will make ping inside a docker container work again.
Fail2ban is meant to run on a host network, which doesn't work in a rootless docker setup. A workaround is using the host fail2ban. However, you need to forward the logs to something that fail2ban can reliably read. I used the /dev/log
UNIX Sock from rsyslog for this. Of course you need to enable rsyslog first. This allow a docker container to forward all logs to the syslog of the host.
Add this to the postfix-mailcow
configuration in the docker-compose.yml
file of mailcow:
logging:
driver: syslog
options:
syslog-address: "unixgram:///dev/log"
Restart the docker compose postfix-mailcow
docker container and you'll see the postfix logs end up in the root /var/log/syslog
log.
The downside is that you'll see the heartbeat end up there as well. This is useless information for the sake of fail2ban. To exclude those lines add this to the /etc/rsyslog.d/50-default.conf
file:
:msg, regex, "postfix.*127.0.0.1" ~
For fail2ban to work edit the jail.local
file you made earlier when hardening SSH and change these blocks to the following:
[postfix]
# To use another modes set filter parameter "mode" in jail.local:
enabled = true
mode = more
port = smtp,465,submission
#logpath = %(postfix_log)s
logpath = /var/log/syslog
backend = %(postfix_backend)s
[postfix-rbl]
enabled = true
filter = postfix[mode=rbl]
port = smtp,465,submission
#logpath = %(postfix_log)s
logpath = /var/log/syslog
backend = %(postfix_backend)s
maxretry = 1
[postfix-sasl]
enabled = true
filter = postfix[mode=auth]
port = smtp,465,submission,imap,imaps,pop3,pop3s
# You might consider monitoring /var/log/mail.warn instead if you are
# running postfix since it would provide the same log lines at the
# "warn" level but overall at the smaller filesize.
logpath = /var/log/syslog
#logpath = %(postfix_log)s
backend = %(postfix_backend)s
And add these two additional ones:
[postfix-openrelay]
enabled = true
port = smtp,465,submission
filter = postfix-openrelay
logpath = /var/log/syslog
[postfix-blacklist]
enabled = true
port = smtp,465,submission
filter = postfix-blacklist
logpath = /var/log/syslog
The postfix-openrelay
block blocks IP's that try to use the mailcow instance as an open relay. The postfix-blacklist
blocks IP's that are recognized by sites like spamhaus fully using IPTables. For both to work create these two files with the respective content.
/etc/fail2ban/filter.d/postfix-blacklist.conf
[Definition]
_daemon = postfix-blacklist
failregex = .*postfix/dnsblog\[.*\].*addr <HOST> listed by domain.*
/etc/fail2ban/filter.d/postfix-openrelay.conf
[Definition]
_daemon = postfix-openrelay
failregex = .*postfix/smtpd\[.*\].*NOQUEUE.*\[<HOST>\].*Relay access denied.*
Restart fail2ban and the host fail2ban will take care of malicious IP's in mailcow.