11 KiB
Building a simple mailserver
WARNING: Adding the docker network's gateway to the list of trusted hosts, e.g. using the network
or connected-networks
option, can create an open relay, for instance if IPv6 is enabled on the host machine but not in Docker. (#1405)
We are going to use this docker based mailserver:
-
First create a directory for the mailserver and get the setup script:
mkdir -p /var/ds/mail.example.org cd /var/ds/mail.example.org/ curl -o setup.sh \ https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh chmod a+x ./setup.sh
-
Create the file
docker-compose.yml
with a content like this:version: '2' services: mail: image: tvial/docker-mailserver:latest hostname: mail domainname: example.org container_name: mail ports: - "25:25" - "587:587" - "465:465" volumes: - ./data/:/var/mail/ - ./state/:/var/mail-state/ - ./config/:/tmp/docker-mailserver/ - /var/ds/wsproxy/letsencrypt/:/etc/letsencrypt/ environment: - PERMIT_DOCKER=network - SSL_TYPE=letsencrypt - ONE_DIR=1 - DMS_DEBUG=1 - SPOOF_PROTECTION=0 - REPORT_RECIPIENT=1 - ENABLE_SPAMASSASSIN=0 - ENABLE_CLAMAV=0 - ENABLE_FAIL2BAN=1 - ENABLE_POSTGREY=0 cap_add: - NET_ADMIN - SYS_PTRACE
For more details about the environment variables that can be used, and their meaning and possible values, check also these:
- https://github.com/tomav/docker-mailserver#environment-variables
- https://github.com/tomav/docker-mailserver/blob/master/.env.dist
Make sure to set the proper
domainname
that you will use for the emails. We forward only SMTP ports (not POP3 and IMAP) because we are not interested in accessing the mailserver directly (from a client). We also use these settings:PERMIT_DOCKER=network
because we want to send emails from other docker containers.SSL_TYPE=letsencrypt
because we will manage SSL certificates with letsencrypt.
-
We need to open these ports on the firewall:
25
,587
,465
ufw allow 25 ufw allow 587 ufw allow 465
On your server you may have to do it differently.
-
Pull the docker image:
docker pull tvial/docker-mailserver:latest
-
Now generate the DKIM keys with
./setup.sh config dkim
and copy the content of the fileconfig/opendkim/keys/domain.tld/mail.txt
on the domain zone configuration at the DNS server. I use bind9 for managing my domains, so I just paste it onexample.org.db
:mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEFACAQ8AMIIBCgKCAQEAaH5KuPYPSF3Ppkt466BDMAFGOA4mgqn4oPjZ5BbFlYA9l5jU3bgzRj3l6/Q1n5a9lQs5fNZ7A/HtY0aMvs3nGE4oi+LTejt1jblMhV/OfJyRCunQBIGp0s8G9kIUBzyKJpDayk2+KJSJt/lxL9Iiy0DE5hIv62ZPP6AaTdHBAsJosLFeAzuLFHQ6USyQRojefqFQtgYqWQ2JiZQ3" "iqq3bD/BVlwKRp5gH6TEYEmx8EBJUuDxrJhkWRUk2VDl1fqhVBy8A9O7Ah+85nMrlOHIFsTaYo9o6+cDJ6t1i6G1gu+bZD0d3/3bqGLPBQV9LyEL1Rona5V7TJBGg099NQkTz1IwIDAQAB" ) ; ----- DKIM key mail for example.org
-
Add these configurations as well on the same file on the DNS server:
mail IN A 10.11.12.13 ; mailservers for example.org 3600 IN MX 1 mail.example.org. ; Add SPF record IN TXT "v=spf1 mx ~all"
Then don't forget to change the serial number and to restart the service.
-
Get an SSL certificate from letsencrypt. I use wsproxy for managing SSL letsencrypt certificates of my domains:
cd /var/ds/wsproxy ds domains-add mail mail.example.org ds get-ssl-cert myemail@gmail.com mail.example.org --test ds get-ssl-cert myemail@gmail.com mail.example.org
Now the certificates will be available on
/var/ds/wsproxy/letsencrypt/live/mail.example.org
. -
Start the mailserver and check for any errors:
apt install docker-compose docker-compose up mail
-
Create email accounts and aliases with
SPOOF_PROTECTION=0
:./setup.sh email add admin@example.org passwd123 ./setup.sh email add info@example.org passwd123 ./setup.sh alias add admin@example.org myemail@gmail.com ./setup.sh alias add info@example.org myemail@gmail.com ./setup.sh email list ./setup.sh alias list
Aliases make sure that any email that comes to these accounts is forwarded to my real email address, so that I don't need to use POP3/IMAP in order to get these messages. Also no anti-spam and anti-virus software is needed, making the mailserver lighter.
-
Or create email accounts and aliases with
SPOOF_PROTECTION=1
:./setup.sh email add admin.gmail@example.org passwd123 ./setup.sh email add info.gmail@example.org passwd123 ./setup.sh alias add admin@example.org admin.gmail@example.org ./setup.sh alias add info@example.org info.gmail@example.org ./setup.sh alias add admin.gmail@example.org myemail@gmail.com ./setup.sh alias add info.gmail@example.org myemail@gmail.com ./setup.sh email list ./setup.sh alias list
This extra step is required to avoid the
553 5.7.1 Sender address rejected: not owned by user
error (the account used for setting up gmail isadmin.gmail@example.org
andinfo.gmail@example.org
) -
Send some test emails to these addresses and make other tests. Then stop the container with
Ctrl+c
and start it again as a daemon:docker-compose up -d mail
. -
Now save on Moodle configuration the SMTP settings and test by trying to send some messages to other users:
- SMTP hosts:
mail.example.org:465
- SMTP security:
SSL
- SMTP username:
info@example.org
- SMTP password:
passwd123
- SMTP hosts:
Using docker-mailserver behind proxy
Information
If you are hiding your container behind a proxy service you might have discovered that the proxied requests from now on contain the proxy IP as the request origin. Whilst this behavior is technical correct it produces certain problems on the containers behind the proxy as they cannot distinguish the real origin of the requests anymore.
To solve this problem on TCP connections we can make use of the proxy protocol. Compared to other workarounds that exist (X-Forwarded-For
which only works for HTTP requests or Tproxy
that requires you to recompile your kernel) the proxy protocol:
- it is protocol agnostic (can work with any layer 7 protocols, even when encrypted).
- it does not require any infrastructure changes
- nat-ing firewalls have no impact it
- it is scalable The is only one condition: both endpoints of the connection MUST be compatible with proxy protocol.
Luckily dovecot
and postfix
are both Proxy-Protocol ready softwares so it depends only on your used reverse-proxy/loadbalancer.
Configuration of the used proxy software
The configuration depends on the used proxy system. I will provide the configuration examples of traefik v2 using IMAP and SMTP with implicit TLS. Feel free to add your configuration if you achived the same goal using different proxy software below:
traefik v2
Truncated configuration of traefik itself:
version: '3.7'
services:
reverse-proxy:
image: traefik:v2.4
container_name: docker-traefik
restart: always
command:
- "--providers.docker"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.smtp.address=:25"
- "--entryPoints.smtp-ssl.address=:465"
- "--entryPoints.imap-ssl.address=:993"
- "--entryPoints.sieve.address=:4190"
ports:
- "25:25"
- "465:465"
- "993:993"
- "4190:4190"
[...]
Truncated list of neccessary labels on the mailserver container:
version: '2'
services:
mail:
image: tvial/docker-mailserver:release-v7.2.0
restart: always
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.smtp.rule=HostSNI(`*`)"
- "traefik.tcp.routers.smtp.entrypoints=smtp"
- "traefik.tcp.routers.smtp.service=smtp"
- "traefik.tcp.services.smtp.loadbalancer.server.port=25"
- "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=1"
- "traefik.tcp.routers.smtp-ssl.rule=HostSNI(`*`)"
- "traefik.tcp.routers.smtp-ssl.entrypoints=smtp-ssl"
- "traefik.tcp.routers.smtp-ssl.service=smtp-ssl"
- "traefik.tcp.services.smtp-ssl.loadbalancer.server.port=465"
- "traefik.tcp.services.smtp-ssl.loadbalancer.proxyProtocol.version=1"
- "traefik.tcp.routers.imap-ssl.rule=HostSNI(`*`)"
- "traefik.tcp.routers.imap-ssl.entrypoints=imap-ssl"
- "traefik.tcp.routers.imap-ssl.service=imap-ssl"
- "traefik.tcp.services.imap-ssl.loadbalancer.server.port=10993"
- "traefik.tcp.services.imap-ssl.loadbalancer.proxyProtocol.version=2"
- "traefik.tcp.routers.sieve.rule=HostSNI(`*`)"
- "traefik.tcp.routers.sieve.entrypoints=sieve"
- "traefik.tcp.routers.sieve.service=sieve"
- "traefik.tcp.services.sieve.loadbalancer.server.port=4190"
[...]
Keep in mind that it is neccessary to use port 10993
here. More information below at dovecot
configuration.
Configuration of the backend (dovecot
and postfix
)
The following changes can be achived completely by adding the content to the appropriate files by using the projects function to overwrite config files.
Changes for postfix
can be applied by adding the following content to config/postfix-main.cf
:
postscreen_upstream_proxy_protocol = haproxy
and to config/postfix-master.cd
:
submission/inet/smtpd_upstream_proxy_protocol=haproxy
smtps/inet/smtpd_upstream_proxy_protocol=haproxy
Changes for dovecot
can be applied by adding the following content to config/dovecot.cf
:
haproxy_trusted_networks = <your-proxy-ip>, <optional-cidr-notation>
haproxy_timeout = 3 secs
service imap-login {
inet_listener imaps {
haproxy = yes
ssl = yes
port = 10993
}
}
Note that port 10993
is used here to avoid conflicts with internal systems like postscreen
and amavis
as they will exchange messages on the default port and obviously have a different origin then compared to the proxy.