mlan/postfix

By mlan

Updated 3 months ago

Postfix (Dovecot) mail server: SMTP, IMAP, POP3, LDAP, MySQL. ENV configuration. Demo included.

Image
Networking

396

The mlan/postfix repo is on GitHub

travis-ci testdocker versionimage sizedocker pullsdocker starsgithub stars

This (non official) repository provides dockerized (MTA) Mail Transfer Agent (SMTP) service using Postfix and Dovecot.

Features

  • MTA (SMTP) server and client Postfix
  • SMTP client authentication on the SMTPS (port 465) and submission (port 587) using Dovecot
  • PostSRSd, sender rewriting scheme
  • Hooks for integrating Let’s Encrypt LTS certificates using the reverse proxy Traefik
  • Consolidated configuration and run data under /srv to facilitate persistent storage
  • Simplified configuration of passwd file authentication, mailbox lookup using environment variables
  • Simplified configuration of LDAP authentication, mailbox and alias lookup using environment variables
  • Simplified configuration of MySQL authentication, mailbox and alias lookup using environment variables
  • Simplified configuration of remote IMAP authentication using environment variables
  • Simplified configuration of SMTP relay using environment variables
  • Simplified configuration of secure SMTP, IMAP and POP3TLS using environment variables
  • Simplified generation of Diffie-Hellman parameters needed for EDH using utility script
  • Multi-staged build providing the images  base and full
  • Configuration using environment variables
  • Log directed to docker daemon with configurable level
  • Built in utility script run helping configuring Postfix and Dovecot
  • Makefile which can build images and do some management and testing
  • Health check
  • Small image size based on Alpine Linux
  • Demo based on docker-compose.yml and Makefile files

Tags

The MAJOR.MINOR.PATCH SemVer is used. In addition to the three number version number you can use two or one number versions numbers, which refers to the latest version of the sub series. The tag latest references the build based on the latest commit to the repository.

The mlan/postfix repository contains a multi staged built. You select which build using the appropriate tag from base and full. The image base only contain Postfix. The image built with the tag full extend base to include Dovecot, which provides mail delivery via IMAP and POP3 and SMTP client authentication as well as integration of Let’s Encrypt TLS certificates using Traefik.

To exemplify the usage of the tags, lets assume that the latest version is 1.0.0. In this case latest, 1.0.0, 1.0, 1, full, full-1.0.0, full-1.0 and full-1 all identify the same image.

Usage

Often you want to configure Postfix and its components. There are different methods available to achieve this. Many aspects can be configured using environment variables described below. These environment variables can be explicitly given on the command line when creating the container. They can also be given in an docker-compose.yml file, see the docker compose example below. Moreover docker volumes or host directories with desired configuration files can be mounted in the container. And finally you can docker exec into a running container and modify configuration files directly.

You can start a mlan/postfix container using the destination domain example.com and table mail boxes for info@example.com and abuse@example.com by issuing the shell command below.

docker run -d --name mta --hostname mx1.example.com -e MAIL_BOXES="info@example.com abuse@example.com" -p 127.0.0.1:25:25 mlan/postfix

One convenient way to test the image is to clone the github repository and run the demo therein, see below.

Docker compose example

An example of how to configure an web mail server using docker compose is given below. It defines 5 services, app, mta, filt, db and auth, which are the web mail server, the mail transfer agent, the SQL database and LDAP authentication respectively.

version: '3'

services:
  app:
    image: mlan/kopano
    networks:
      - backend
    ports:
      - "127.0.0.1:8008:80"    # WebApp & EAS (alt. HTTP)
      - "127.0.0.1:143:143"    # IMAP (not needed if all devices can use EAS)
      - "127.0.0.1:110:110"    # POP3 (not needed if all devices can use EAS)
      - "127.0.0.1:8080:8080"  # ICAL (not needed if all devices can use EAS)
      - "127.0.0.1:993:993"    # IMAPS (not needed if all devices can use EAS)
      - "127.0.0.1:995:995"    # POP3S (not needed if all devices can use EAS)
      - "127.0.0.1:8443:8443"  # ICALS (not needed if all devices can use EAS)
    depends_on:
      - auth
      - db
      - mta
    environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
      - USER_PLUGIN=ldap
      - LDAP_URI=ldap://auth:389/
      - MYSQL_HOST=db
      - SMTP_SERVER=mta
      - LDAP_SEARCH_BASE=${AD_BASE-dc=example,dc=com}
      - LDAP_USER_TYPE_ATTRIBUTE_VALUE=${AD_USR_OB-kopano-user}
      - LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${AD_GRP_OB-kopano-group}
      - LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn
      - LDAP_PROPMAP=
      - DAGENT_PLUGINS=movetopublicldap
      - MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
      - MYSQL_USER=${MYSQL_USER-kopano}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
      - IMAP_LISTEN=*:143                       # also listen to eth0
      - POP3_LISTEN=*:110                       # also listen to eth0
      - ICAL_LISTEN=*:8080                      # also listen to eth0
      - IMAPS_LISTEN=*:993                      # enable TLS
      - POP3S_LISTEN=*:995                      # enable TLS
      - ICALS_LISTEN=*:8443                     # enable TLS
      - PLUGIN_SMIME_USER_DEFAULT_ENABLE_SMIME=true
      - SYSLOG_LEVEL=${SYSLOG_LEVEL-3}
      - LOG_LEVEL=${LOG_LEVEL-3}
    volumes:
      - app-conf:/etc/kopano
      - app-atch:/var/lib/kopano/attachments
      - app-sync:/var/lib/z-push
      - app-spam:/var/lib/kopano/spamd          # kopano-spamd integration
      - /etc/localtime:/etc/localtime:ro        # Use host timezone
    cap_add: # helps debugging by allowing strace
      - sys_ptrace

  mta:
    image: mlan/postfix
    hostname: ${MAIL_SRV-mx}.${MAIL_DOMAIN-example.com}
    networks:
      - backend
    ports:
      - "127.0.0.1:25:25"      # SMTP
      - "127.0.0.1:465:465"    # SMTPS authentication required
    depends_on:
      - auth
    environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
      - MESSAGE_SIZE_LIMIT=${MESSAGE_SIZE_LIMIT-25600000}
      - LDAP_HOST=auth
      - VIRTUAL_TRANSPORT=lmtp:app:2003
      - SMTPD_MILTERS=inet:flt:11332
      - MILTER_DEFAULT_ACTION=accept
      - SMTP_RELAY_HOSTAUTH=${SMTP_RELAY_HOSTAUTH-}
      - SMTP_TLS_SECURITY_LEVEL=${SMTP_TLS_SECURITY_LEVEL-}
      - SMTP_TLS_WRAPPERMODE=${SMTP_TLS_WRAPPERMODE-no}
      - SMTPD_USE_TLS=yes
      - LDAP_USER_BASE=ou=${AD_USR_OU-users},${AD_BASE-dc=example,dc=com}
      - LDAP_QUERY_FILTER_USER=(&(objectclass=${AD_USR_OB-kopano-user})(mail=%s))
      - LDAP_QUERY_FILTER_ALIAS=(&(objectclass=${AD_USR_OB-kopano-user})(kopanoAliases=%s))
      - LDAP_QUERY_ATTRS_PASS=uid=user
      - REGEX_ALIAS=${REGEX_ALIAS-}
    volumes:
      - mta:/srv
      - app-spam:/var/lib/kopano/spamd          # kopano-spamd integration
      - /etc/localtime:/etc/localtime:ro        # Use host timezone
    cap_add: # helps debugging by allowing strace
      - sys_ptrace

  flt:
    image: mlan/rspamd
    networks:
      - backend
    ports:
      - "127.0.0.1:11334:11334" # HTML rspamd WebGui
    depends_on:
      - mta
    environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
      - WORKER_CONTROLLER=enable_password="${FLT_PASSWD-secret}";
      - METRICS=${FLT_METRIC}
      - CLASSIFIER_BAYES=${FLT_BAYES}
      - MILTER_HEADERS=${FLT_HEADERS}
      - DKIM_DOMAIN=${MAIL_DOMAIN-example.com}
      - DKIM_SELECTOR=${DKIM_SELECTOR-default}
      - SYSLOG_LEVEL=${SYSLOG_LEVEL-}
      - LOGGING=level="${FLT_LOGGING-error}";
    volumes:
      - flt:/srv
      - app-spam:/var/lib/kopano/spamd          # kopano-spamd integration
      - /etc/localtime:/etc/localtime:ro        # Use host timezone
    cap_add: # helps debugging by allowing strace
      - sys_ptrace

  db:
    image: mariadb
    command: ['--log_warnings=1']
    networks:
      - backend
    environment:
      - LANG=C.UTF-8
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD-secret}
      - MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
      - MYSQL_USER=${MYSQL_USER-kopano}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
    volumes:
      - db:/var/lib/mysql
      - /etc/localtime:/etc/localtime:ro        # Use host timezone

  auth:
    image: mlan/openldap
    networks:
      - backend
    command: --root-cn ${AD_ROOT_CN-admin} --root-pw ${AD_ROOT_PW-secret}
    environment:
      - LDAPBASE=${AD_BASE-dc=example,dc=com}
      - LDAPDEBUG=${AD_DEBUG-parse}
    volumes:
      - auth:/srv
      - /etc/localtime:/etc/localtime:ro        # Use host timezone

networks:
  backend:

volumes:
  app-atch:
  app-conf:
  app-spam:
  app-sync:
  auth:
  db:
  mta:
  flt:

Demo

This repository contains a demo directory which hold the docker-compose.yml file as well as a Makefile which might come handy. Start with cloning the github repository.

git clone https://github.com/mlan/docker-postfix.git

From within the demo directory you can start the containers by typing:

make init

Then you can assess WebApp on the URL http://localhost:8008 and log in with the user name demo and password demo .

make web

You can send yourself a test email by typing:

make test

When you are done testing you can destroy the test containers by typing

make destroy

Persistent storage

By default, docker will store the configuration and run data within the container. This has the drawback that the configurations and queued and quarantined mail are lost together with the container should it be deleted. It can therefore be a good idea to use docker volumes and mount the run directories and/or the configuration directories there so that the data will survive a container deletion.

To facilitate such approach, to achieve persistent storage, the configuration and run directories of the services has been consolidated to /srv/etc and /srv/var respectively. So if you to have chosen to use both persistent configuration and run data you can run the container like this:

docker run -d --name mta -v mta:/srv -p 127.0.0.1:25:25 mlan/postfix

When you start a container which creates a new volume, as above, and the container has files or directories in the directory to be mounted (such as /srv/ above), the directory’s contents are copied into the volume. The container then mounts and uses the volume, and other containers which use the volume also have access to the pre-populated content. More details here.

Configuration / seeding procedure

The mlan/postfix image contains an elaborate configuration / seeding procedure. The configuration is controlled by environment variables, described below.

The seeding procedure will leave any existing configuration untouched. This is achieved by the using an unlock file: DOCKER_UNLOCK_FILE=/srv/etc/.docker.unlock. During the image build this file is created. When the the container is started the configuration / seeding procedure will be executed if the DOCKER_UNLOCK_FILE can be found. Once the procedure completes the unlock file is deleted preventing the configuration / seeding procedure to run when the container is restarted.

The unlock file approach was selected since it is difficult to accidentally create a file.

In the rare event that want to modify the configuration of an existing container you can override the default behavior by setting FORCE_CONFIG=OVERWRITE to a no-empty string.

Environment variables

When you create the mlan/postfix container, you can configure the services by passing one or more environment variables or arguments on the docker run command line. Once the services has been configured a lock file is created, to avoid repeating the configuration procedure when the container is restated.

To see all available Postfix configuration variables you can run postconf within the container, for example like this:

docker-compose exec mta postconf

If you do, you will notice that configuration variable names are all lower case, but they will be matched with all uppercase environment variables by the container initialization scripts.

Similarly Dovecot configuration variables can be set. One difference is that, to avoid name clashes, the variables are prefixed by DOVECOT_PREFIX=DOVECOT_. You can list all Dovecot variables by typing:

docker-compose exec mta doveconf

Milter support

Postfix communicates with external applications like mail filters (Milters), providing spam filtering, using the Milter protocol, which is similar to SMTP.

Rspamd is a fast, free and open-source spam filtering system, which has been tested with mlan/postfix. The docker-postfix repository provides a dockerized version of the Rspamd mail filter.

SMTPD_MILTERS

Communication with the Rspamd milter is configured by setting SMTPD_MILTERS=inet:flt:11332, which assumes that a Rspamd container, named flt, is reachable on the custom network.

MILTER_DEFAULT_ACTION

The milter_default_action parameter specifies how Postfix handles Milter application errors. You can set MILTER_DEFAULT_ACTION=accept to proceed as if the mail filter was not present, when there are errors.

Outgoing SMTP relay

Sometimes you want outgoing email to be sent to a SMTP relay and not directly to its destination. This could for instance be when your ISP is blocking port 25 or perhaps if you have a dynamic IP and are afraid of that mail servers will drop your outgoing emails because of that.

SMTP_RELAY_HOSTAUTH

This environment variable simplify a SMTP relay configuration. The SMTP relay host might require SASL authentication in which case user name and password can also be given in variable. The format is "host:port user:passwd". Example: SMTP_RELAY_HOSTAUTH="[example.relay.com]:587 e863ac2bc1e90d2b05a47b2e5c69895d:b35266f99c75d79d302b3adb42f3c75f"

SMTP_TLS_SECURITY_LEVEL

You can enforce the use of TLS, so that the Postfix SMTP server announces STARTTLS and accepts no mail without TLS encryption, by setting SMTP_TLS_SECURITY_LEVEL=encrypt. Default: SMTP_TLS_SECURITY_LEVEL=none.

SMTP_TLS_WRAPPERMODE

To configure the Postfix SMTP client connecting using the legacy SMTPS protocol instead of using the STARTTLS command, set SMTP_TLS_WRAPPERMODE=yes. This mode requires SMTP_TLS_SECURITY_LEVEL=encrypt or stronger. Default: SMTP_TLS_WRAPPERMODE=no

Forwarding rewrite

PostSRSd, implementing a sender rewriting scheme (SRS), offer optional forwarding rewrite to avoid receiving servers flagging messages as spam.

Incoming SMTPS and submission client authentication

Postfix achieves client authentication using SASL provided by Dovecot. Client authentication is the mechanism that is used on SMTP relay using SASL authentication, see the SMTP_RELAY_HOSTAUTH. Here the client authentication is arranged on the smtps port: 465 and submission port: 587.

To avoid the risk of being an open relay the SMTPS and submission (MSA) services are only activated when at least one SASL method has activated. Four methods are supported; LDAP, MySQL, IMAP and password file. Any combination of methods can simultaneously be active. If more than one method is active, all authentication methods are attempted one after another.

A method is activated when its required variables has been defined. For LDAP, LDAP_QUERY_ATTRS_PASS is needed in addition to the LDAP variables discussed in LDAP mailbox lookup. MySQL needs MYSQL_QUERY_PASS in addition to the MySQL variables discussed in MySQL mailbox lookup. And IMAP needs the SMTPD_SASL_IMAPHOST variable and password file require SMTPD_SASL_CLIENTAUTH.

Additionally clients are required to authenticate using TLS to avoid password being sent in the clear. The configuration of the services are the similar with the exception that the SMTPS service uses the legacy SMTPS protocol; SMTPD_TLS_WRAPPERMODE=yes, whereas the submission service uses the STARTTLS protocol.

Password file SASL client authentication SMTPD_SASL_CLIENTAUTH

You can list clients and their passwords in a space separated string using the format: "username:{scheme}passwd". Example: SMTPD_SASL_CLIENTAUTH="client1:{plain}passwd1 client2:{plain}passwd2". For security you might want to use encrypted passwords. One way to encrypt a password ({plain}secret) is by running

docker exec -it mta doveadm pw -p secret

{CRYPT}$2y$05$Osj5ebALV/bXo18H4BKLa.J8Izn23ilI8TNA/lIHz92TuQFbZ/egK

for use in SMTPD_SASL_CLIENTAUTH.

LDAP SASL client authentication LDAP_QUERY_ATTRS_PASS

Using LDAP with authentication binds, Dovecot, binds, using the SMTPS client credentials, to the LDAP server which that verifies the them. See LDAP for more details.

The LDAP client configurations described in LDAP mailbox lookup are also used here. In addition to these, the binding <user> attribute needs to be specified using LDAP_QUERY_ATTRS_PASS. The <user> attribute is defined in this way LDAP_QUERY_ATTRS_PASS=<user>=user. To exemplify, if uid is the desired <user> attribute define LDAP_QUERY_ATTRS_PASS=uid=user.

LDAP_QUERY_FILTER_PASS

Dovecot sends a LDAP request defined by LDAP_QUERY_FILTER_PASS to lookup the DN that will be used for the authentication bind. Example: LDAP_QUERY_FILTER_PASS=(&(objectclass=posixAccount)(uid=%u)).

LDAP_QUERY_FILTER_PASS can be omitted in which case the filter is being reconstructed from LDAP_QUERY_FILTER_USER. The reconstruction tries to replace the string (mail=%s) in LDAP_QUERY_FILTER_USER with (<user>=%u), where <user> is taken from LDAP_QUERY_ATTRS_PASS. Example: LDAP_QUERY_FILTER_USER=(&(objectclass=posixAccount)(mail=%s)) and LDAP_QUERY_ATTRS_PASS=uid=user will result in this filter (&(objectclass=posixAccount)(uid=%u)).

IMAP SASL client authentication SMTPD_SASL_IMAPHOST

Dovecot, can authenticate users against a remote IMAP server (RIMAP). For this to work it is sufficient to provide the address of the IMAP host, by using SMTPD_SASL_IMAPHOST. Examples SMTPD_SASL_IMAPHOST=app, SASL_IMAP_HOST=192.168.1.123:143.

For more details see Authentication via remote IMAP server.

Incoming destination domain

Postfix is configured to be the final destination of the virtual/hosted domains defined by the environment variable MAIL_DOMAIN. If the domains are not properly configured Postfix will be rejecting the emails. When multiple domains are used the first domain in the list is considered to be the primary one.

MAIL_DOMAIN

The default value of MAIL_DOMAIN=$(hostname -d) is to use the host name of the container minus the first component. So you can either use the environment variable MAIL_DOMAIN or the argument --hostname. So for example, --hostname mx1.example.com or -e MAIL_DOMAIN="example.com secondary.com" .

Incoming TLS support

Transport Layer Security (TLS, formerly called SSL) provides certificate-based authentication and encrypted sessions. An encrypted session protects the information that is transmitted with SMTP mail or with SASL authentication.

Here TLS is activated for inbound messages when either SMTPD_TLS_CHAIN_FILES or SMTPD_TLS_CERT_FILE (or its DSA and ECDSA counterparts) is not empty or SMTPD_USE_TLS=yes. The Postfix SMTP server generally needs a certificate and a private key to provide TLS. Both must be in PEM format. The private key must not be encrypted, meaning: the key must be accessible without a password. The RSA certificate and a private key files are identified by SMTPD_TLS_CERT_FILE and SMTPD_TLS_KEY_FILE.

SMTPD_USE_TLS=yes

If SMTPD_USE_TLS=yes is explicitly defined but there are no certificate files defined, a self-signed certificate will be generated when the container is created.

SMTPD_TLS_CERT_FILE

Specifies the RSAPEM certificate file within the container to be used with incoming TLS connections. The certificate file need to be made available in the container by some means. Example SMTPD_TLS_CERT_FILE=cert.pem. Additionally there are the DSA, ECDSA or chain counterparts; SMTPD_TLS_DCERT_FILE, SMTPD_TLS_ECCERT_FILE and SMTPD_TLS_CHAIN_FILES.

SMTPD_TLS_KEY_FILE

Specifies the RSA PEM private key file within the container to be used with incoming TLS connections. The private key file need to be made available in the container by some means. Example SMTPD_TLS_KEY_FILE=key.pem. Additionally there are the DSA, ECDSA or chain counterparts; SMTPD_TLS_DKEY_FILE, SMTPD_TLS_ECKEY_FILE and SMTPD_TLS_CHAIN_FILES.

TLS forward secrecy

The term "Forward Secrecy" (or sometimes "Perfect Forward Secrecy") is used to describe security protocols in which the confidentiality of past traffic is not compromised when long-term keys used by either or both sides are later disclosed.

Forward secrecy is accomplished by negotiating session keys using per-session cryptographically-strong random numbers that are not saved, and signing the exchange with long-term authentication keys. Later disclosure of the long-term keys allows impersonation of the key holder from that point on, but not recovery of prior traffic, since with forward secrecy, the discarded random key agreement inputs are not available to the attacker.

The built in utility script run can be used to generate the Diffie-Hellman parameters needed for forward secrecy.

docker exec -it mta run postfix_update_dhparam
Let’s Encrypt LTS certificates using Traefik

Let’s Encrypt provide

Docker Pull Command

docker pull mlan/postfix