Major update, better support for handling TLS connections.

This update includes a few features that make it easier to work
with relay hosts. The configuration now uses built-in (Alpine) TLS
certificates and allows the user to specify how the Postfix should
establish a TLS connection. Configure it using `RELAYHOST_TLS_LEVEL`,
if needed.

Another configuration option was added, `MESSAGE_SIZE_LIMIT`, which
enables you to reject messages exceeding certain limits. Especially
useful with relay servers, to prevent messages getting stuck in
Postfix queue.

Use the `INBOUND_DEBUGGING` parameter to enable additional postfix
logs for incoming messages when things go south.

Use `TZ` parameter to set the timezone of the container (especially
useful for logs and dates in the messages).

*Notice that `ALLOWED_SENDER_DOMAINS` is now a neccessary parameter.*
Turns out that due to Postfix anti-spam configuration there's no way
to start it properly without specifying the domains it will relay
email for.

This commit also includes a few samples of running the container:
- standalone
- in [docker-compose](https://docs.docker.com/compose/)

Further work: add a working Kubernetes / Helm chart example.
This commit is contained in:
Bojan Čekrlić 2019-01-02 10:10:17 +01:00
parent 9254d5ef36
commit aa66569ac1
7 changed files with 178 additions and 35 deletions

View file

@ -3,6 +3,8 @@ MAINTAINER Bojan Cekrlic - https://github.com/bokysan/docker-postfix/
# See README.md for details # See README.md for details
# Set the timezone for the container, if needed.
ENV TZ=
# Postfix myhostname # Postfix myhostname
ENV HOSTNAME= ENV HOSTNAME=
# Host that relays your msgs # Host that relays your msgs
@ -11,14 +13,21 @@ ENV RELAYHOST=
ENV RELAYHOST_USERNAME= ENV RELAYHOST_USERNAME=
# An (optional) login password for the relay server # An (optional) login password for the relay server
ENV RELAYHOST_PASSWORD= ENV RELAYHOST_PASSWORD=
# Define relay host TLS connection level. See http://www.postfix.org/postconf.5.html#smtp_tls_security_level for details.
# By default, the permissive level ("may") is used, if not defined.
ENV RELAYHOST_TLS_LEVEL=
# Allow domains from per Network ( default 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 ) # Allow domains from per Network ( default 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 )
ENV MYNETWORKS=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 ENV MYNETWORKS=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
# Allow any sender domains # Allow any sender domains
ENV ALLOWED_SENDER_DOMAINS= ENV ALLOWED_SENDER_DOMAINS=
# Attachments size. 0 means unlimited. Usually needs to be set if your relay host has an attachment size limit
ENV MESSAGE_SIZE_LIMIT=
# Enable additional debugging for connections to postfix
ENV INBOUND_DEBUGGING=
# Install supervisor, postfix and bash (because run.sh is not sh-complatible) # Install supervisor, postfix
RUN true && \ RUN true && \
apk add --no-cache --update postfix ca-certificates supervisor rsyslog bash && \ apk add --no-cache --update postfix ca-certificates tzdata supervisor rsyslog && \
apk add --no-cache --upgrade musl musl-utils && \ apk add --no-cache --upgrade musl musl-utils && \
(rm "/tmp/"* 2>/dev/null || true) && (rm -rf /var/cache/apk/* 2>/dev/null || true) (rm "/tmp/"* 2>/dev/null || true) && (rm -rf /var/cache/apk/* 2>/dev/null || true)

View file

@ -18,7 +18,7 @@ This is a _server side_ POSTFIX image, geared towards emails that need to be sen
To run the container, do the following: To run the container, do the following:
``` ```
docker run --rm --name postfix -p 1587:587 boky/postfix docker run --rm --name postfix -e "ALLOWED_SENDER_DOMAINS=example.com" -p 1587:587 boky/postfix
``` ```
You can now send emails by using `localhost:1587` as your SMTP server address. **Please note that You can now send emails by using `localhost:1587` as your SMTP server address. **Please note that
@ -76,6 +76,22 @@ If your end server requires you to authenticate with username/password, add them
docker run --rm --name postfix -e RELAYHOST=mail.google.com -e RELAYHOST_USERNAME=hello@gmail.com -e RELAYHOST_PASSWORD=world -p 1587:587 boky/postfix docker run --rm --name postfix -e RELAYHOST=mail.google.com -e RELAYHOST_USERNAME=hello@gmail.com -e RELAYHOST_PASSWORD=world -p 1587:587 boky/postfix
``` ```
### `RELAYHOST_TLS_LEVEL`
Define relay host TLS connection level. See http://www.postfix.org/postconf.5.html#smtp_tls_security_level for details. By default, the permissive level ("may") is used, which basically means "use TLS if available" and should be a sane default in most cases.
This level defines how the postfix will connect to your upstream server.
### `MESSAGE_SIZE_LIMIT`
Define the maximum size of the message, in bytes.
See more in [Postfix documentation](http://www.postfix.org/postconf.5.html#message_size_limit).
By default, this limit is set to 0 (zero), which means unlimited. Why would you want to set this? Well, this is especially useful in relation
with `RELAYHOST` setting. If your relay host has a message limit (and usually it does), set it also here. This will help you "fail fast" --
your message will be rejected at the time of sending instead having it stuck in the outbound queue indefenetly.
### `MYNETWORKS` ### `MYNETWORKS`
This implementation is meant for private installations -- so that when you configure your services using _docker compose_ This implementation is meant for private installations -- so that when you configure your services using _docker compose_
@ -92,18 +108,23 @@ docker run --rm --name postfix -e "MYNETWORKS=10.1.2.0/24" -p 1587:587 boky/post
### `ALLOWED_SENDER_DOMAINS` ### `ALLOWED_SENDER_DOMAINS`
If your application is sending email from just a few domains (and most application do), it is a good practice to lock the Due to in-built spam protection in [Postfix](http://www.postfix.org/postconf.5.html#smtpd_relay_restrictions) you will need to specify
POSTFIX further down and accept email from these domains only. sender domains -- the domains you are using to send your emails from, otherwise Postfix will refuse to start.
Example: Example:
``` ```
docker run --rm --name postfix -e "ALLOWED_SENDER_DOMAINS=example.com example.org" -p 1587:587 boky/postfix docker run --rm --name postfix -e "ALLOWED_SENDER_DOMAINS=example.com example.org" -p 1587:587 boky/postfix
``` ```
### `INBOUND_DEBUGGING`
Enable additional debugging for any connection comming from `MYNETWORKS`. Set to a non-empty string (usually "1" or "yes") to
enable debugging.
## Extending the image ## Extending the image
If you need to add custom configuration to postfix or have it do something outside of the scope of this configuration, simply If you need to add custom configuration to postfix or have it do something outside of the scope of this configuration, simply
add your scripts to `/docker-init.db/`. All files with the `.sh` extension will be executed automatically at the end of the add your scripts to `/docker-init.db/`: All files with the `.sh` extension will be executed automatically at the end of the
startup script. startup script.
E.g.: create a custom `Dockerfile` like this: E.g.: create a custom `Dockerfile` like this:
@ -115,6 +136,15 @@ ADD Dockerfiles/additional-config.sh /docker-init.db/
Build it with docker and your script will be automatically executed before Postfix starts. Build it with docker and your script will be automatically executed before Postfix starts.
Or -- alternately -- bind this folder in your docker config and put your scripts there. Useful if you need to add additional config
to your postfix server or override configs created by the script.
For example, your script could contain something like this:
```
#!/bin/sh
postconf -e "address_verify_negative_cache=yes"
```
## Security ## Security
Postfix will run the master proces as `root`, because that's how it's designed. Subprocesses will run under the `postfix` account Postfix will run the master proces as `root`, because that's how it's designed. Subprocesses will run under the `postfix` account

139
run.sh
View file

@ -1,8 +1,67 @@
#!/bin/sh #!/bin/sh
echo "******************************" reset=""
echo "**** POSTFIX STARTING UP *****" yellow=""
echo "******************************" yellow_bold=""
red=""
orange=""
# Returns 0 if the specified string contains the specified substring, otherwise returns 1.
# This exercise it required because we are using the sh-compatible interpretation instead
# of bash.
contains() {
string="$1"
substring="$2"
if test "${string#*$substring}" != "$string"
then
return 0 # $substring is in $string
else
return 1 # $substring is not in $string
fi
}
if test -t 1; then
# Quick and dirty test for color support
if contains "$TERM" "256" || contains "$COLORTERM" "256" || contains "$COLORTERM" "color" || contains "$COLORTERM" "24bit"; then
reset="\033[0m"
green="\033[38;5;46m"
yellow="\033[38;5;178m"
red="\033[91m"
orange="\033[38;5;208m"
emphasis="\033[38;5;226m"
elif contains "$TERM" "xterm"; then
reset="\033[0m"
green="\033[32m"
yellow="\033[33m"
red="\033[31;1m"
orange="\033[31m"
emphasis="\033[33;1m"
fi
fi
info="${green}INFO:${reset}"
notice="${yellow}NOTE:${reset}"
warn="${orange}WARN:${reset}"
echo -e "******************************"
echo -e "**** POSTFIX STARTING UP *****"
echo -e "******************************"
# Check if we need to configure the container timezone
if [ ! -z "$TZ" ]; then
TZ_FILE="/usr/share/zoneinfo/$TZ"
if [ -f "$TZ_FILE" ]; then
echo -e "$notice Setting container timezone to: ${emphasis}$TZ${reset}"
ln -snf "$TZ_FILE" /etc/localtime
echo "$TZ" > /etc/timezone
else
echo -e "$warn Cannot set timezone to: ${emphasis}$TZ${reset} -- this timezone does not exist."
fi
else
echo -e "$info Not setting any timezone for the container"
fi
# Make and reown postfix folders # Make and reown postfix folders
mkdir -p /var/spool/postfix/ && mkdir -p /var/spool/postfix/pid mkdir -p /var/spool/postfix/ && mkdir -p /var/spool/postfix/pid
@ -20,41 +79,58 @@ postconf -e mydestination=
# Don't relay for any domains # Don't relay for any domains
postconf -e relay_domains= postconf -e relay_domains=
# As this is a server-based service, allow any message size -- we hope the sender knows if [ ! -z "$MESSAGE_SIZE_LIMIT" ]; then
# what he is doing echo -e "$notice Restricting message_size_limit to: ${emphasis}$MESSAGE_SIZE_LIMIT bytes${reset}"
postconf -e "message_size_limit=0" postconf -e "message_size_limit=$MESSAGE_SIZE_LIMIT"
else
# As this is a server-based service, allow any message size -- we hope the sender knows
# what he is doing
echo -e "$info Using ${emphasis}unlimited${reset} message size."
postconf -e "message_size_limit=0"
fi
# Reject invalid HELOs # Reject invalid HELOs
postconf -e smtpd_delay_reject=yes postconf -e smtpd_delay_reject=yes
postconf -e smtpd_helo_required=yes postconf -e smtpd_helo_required=yes
postconf -e "smtpd_helo_restrictions=permit_mynetworks,reject_invalid_helo_hostname,permit" postconf -e "smtpd_helo_restrictions=permit_mynetworks,reject_invalid_helo_hostname,permit"
postconf -e "smtpd_sender_restrictions=permit_mynetworks"
# Set up host name # Set up host name
if [ ! -z "$HOSTNAME" ]; then if [ ! -z "$HOSTNAME" ]; then
echo -e "$notice Setting myhostname: ${emphasis}$HOSTNAME${reset}"
postconf -e myhostname="$HOSTNAME" postconf -e myhostname="$HOSTNAME"
else else
postconf -# myhostname postconf -# myhostname
fi fi
if [ -z "$RELAYHOST_TLS_LEVEL" ]; then
echo -e "$info Setting smtp_tls_security_level: ${emphasis}may${reset}"
postconf -e "smtp_tls_security_level=may"
else
echo -e "$notice Setting smtp_tls_security_level: ${emphasis}$RELAYHOST_TLS_LEVEL${reset}"
postconf -e "smtp_tls_security_level=$RELAYHOST_TLS_LEVEL"
fi
# Set up a relay host, if needed # Set up a relay host, if needed
if [ ! -z "$RELAYHOST" ]; then if [ ! -z "$RELAYHOST" ]; then
echo -n "- Forwarding all emails to $RELAYHOST" echo -en "$notice Forwarding all emails to ${emphasis}$RELAYHOST${reset}"
postconf -e relayhost=$RELAYHOST postconf -e "relayhost=$RELAYHOST"
# Alternately, this could be a folder, like this:
# smtp_tls_CApath
postconf -e "smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt"
if [ -n "$RELAYHOST_USERNAME" ] && [ -n "$RELAYHOST_PASSWORD" ]; then if [ -n "$RELAYHOST_USERNAME" ] && [ -n "$RELAYHOST_PASSWORD" ]; then
echo " using username $RELAYHOST_USERNAME." echo -e " using username ${emphasis}$RELAYHOST_USERNAME${reset} and password ${emphasis}(redacted)${reset}."
echo "$RELAYHOST $RELAYHOST_USERNAME:$RELAYHOST_PASSWORD" >> /etc/postfix/sasl_passwd echo "$RELAYHOST $RELAYHOST_USERNAME:$RELAYHOST_PASSWORD" >> /etc/postfix/sasl_passwd
postmap hash:/etc/postfix/sasl_passwd postmap hash:/etc/postfix/sasl_passwd
postconf -e "smtp_sasl_auth_enable=yes" postconf -e "smtp_sasl_auth_enable=yes"
postconf -e "smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd" postconf -e "smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd"
postconf -e "smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt"
postconf -e "smtp_enforce_tls=yes"
postconf -e "smtp_sasl_security_options=noanonymous" postconf -e "smtp_sasl_security_options=noanonymous"
else else
echo " without any authentication. Make sure your server is configured to accept emails coming from this IP." echo -e " without any authentication. ${emphasis}Make sure your server is configured to accept emails coming from this IP.${reset}"
fi fi
else else
echo "- Will try to deliver emails directly to the final server. Make sure your DNS is setup properly!" echo -e "$notice Will try to deliver emails directly to the final server. ${emphasis}Make sure your DNS is setup properly!${reset}"
postconf -# relayhost postconf -# relayhost
postconf -# smtp_sasl_auth_enable postconf -# smtp_sasl_auth_enable
postconf -# smtp_sasl_password_maps postconf -# smtp_sasl_password_maps
@ -62,19 +138,27 @@ else
fi fi
if [ ! -z "$MYNETWORKS" ]; then if [ ! -z "$MYNETWORKS" ]; then
postconf -e mynetworks=$MYNETWORKS echo -e "$notice Using custom allowed networks: ${emphasis}$MYNETWORKS${reset}"
else else
postconf -e "mynetworks=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" echo -e "$info Using default private network list for trusted networks."
MYNETWORKS="127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
fi
postconf -e "mynetworks=$MYNETWORKS"
if [ ! -z "$INBOUND_DEBUGGING" ]; then
echo -e "$notice Enabling additional debbuging for: ${emphasis}$MYNETWORKS${reset}"
postconf -e "debug_peer_list=$MYNETWORKS"
fi fi
# Split with space # Split with space
if [ ! -z "$ALLOWED_SENDER_DOMAINS" ]; then if [ ! -z "$ALLOWED_SENDER_DOMAINS" ]; then
echo -n "- Setting up allowed SENDER domains:" echo -en "$notice Setting up allowed SENDER domains:"
allowed_senders=/etc/postfix/allowed_senders allowed_senders=/etc/postfix/allowed_senders
rm -f $allowed_senders $allowed_senders.db > /dev/null rm -f $allowed_senders $allowed_senders.db > /dev/null
touch $allowed_senders touch $allowed_senders
for i in $ALLOWED_SENDER_DOMAINS; do for i in $ALLOWED_SENDER_DOMAINS; do
echo -n " $i" echo -ne " ${emphasis}$i${reset}"
echo -e "$i\tOK" >> $allowed_senders echo -e "$i\tOK" >> $allowed_senders
done done
echo echo
@ -86,31 +170,28 @@ if [ ! -z "$ALLOWED_SENDER_DOMAINS" ]; then
# will be able to send out emails much faster, as there will be no lookup and lockup if the target server is not responing or available. # will be able to send out emails much faster, as there will be no lookup and lockup if the target server is not responing or available.
# postconf -e "smtpd_recipient_restrictions=reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unverified_recipient, check_sender_access hash:$allowed_senders, reject" # postconf -e "smtpd_recipient_restrictions=reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unverified_recipient, check_sender_access hash:$allowed_senders, reject"
postconf -e "smtpd_recipient_restrictions=reject_non_fqdn_recipient, reject_unknown_recipient_domain, check_sender_access hash:$allowed_senders, reject" postconf -e "smtpd_recipient_restrictions=reject_non_fqdn_recipient, reject_unknown_recipient_domain, check_sender_access hash:$allowed_senders, reject"
else
postconf -# "smtpd_restriction_classes"
# Update: loosen up on RCPT checks. This will mean we might get some emails which are not valid, but the service connecting
# will be able to send out emails much faster, as there will be no lookup and lockup if the target server is not responing or available.
# postconf -e "smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,reject_unverified_recipient"
postconf -e "smtpd_recipient_restrictions=reject_non_fqdn_recipient, reject_unknown_recipient_domain, defer_unauth_destination, permit"
fi
# Since we are behind closed doors, let's just permit all relays. # Since we are behind closed doors, let's just permit all relays.
postconf -e "smtpd_relay_restrictions=permit" postconf -e "smtpd_relay_restrictions=permit"
else
echo -e "ERROR: You need to specify sender domains otherwise Postfix will not run!"
exit 1
fi
# Use 587 (submission) # Use 587 (submission)
sed -i -r -e 's/^#submission/submission/' /etc/postfix/master.cf sed -i -r -e 's/^#submission/submission/' /etc/postfix/master.cf
if [ -d /docker-init.db/ ]; then if [ -d /docker-init.db/ ]; then
echo "- Executing any found custom scripts..." echo -e "$notice Executing any found custom scripts..."
for f in /docker-init.db/*; do for f in /docker-init.db/*; do
case "$f" in case "$f" in
*.sh) chmod +x "$f"; echo -e"\trunning $f"; . "$f" ;; *.sh) chmod +x "$f"; echo -e "\trunning ${emphasis}$f${reset}"; . "$f" ;;
*) echo "$0: ignoring $f" ;; *) echo "$0: ignoring $f" ;;
esac esac
done done
fi fi
echo "- Staring rsyslog and postfix" echo -e "$notice Staring ${emphasis}rsyslog${reset} and ${emphasis}postfix${reset}"
exec supervisord -c /etc/supervisord.conf exec supervisord -c /etc/supervisord.conf

4
sample/command-line/start.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
cd $(dirname $0)/../../
docker build . -t boky/postfix && \
docker run -it --rm --name postfix -p 1587:587 $* boky/postfix

View file

@ -0,0 +1,9 @@
version: "3.7"
services:
smtp-relay:
build: ../..
restart: always
env_file:
- "./sample.env"
expose:
- "587"

View file

@ -0,0 +1,7 @@
TZ=Europe/Amsterdam
HOSTNAME=smtp-relay
RELAYHOST=smtp.gmail.com:587
RELAYHOST_USERNAME=you@gmail.com
RELAYHOST_PASSWORD=yourgmailapppassword
MESSAGE_SIZE_LIMIT=26214400
ALLOWED_SENDER_DOMAINS=example.org

3
sample/docker-compose/start.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
cd $(dirname $0)
docker-compose up