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
# Set the timezone for the container, if needed.
ENV TZ=
# Postfix myhostname
ENV HOSTNAME=
# Host that relays your msgs
@ -11,14 +13,21 @@ ENV RELAYHOST=
ENV RELAYHOST_USERNAME=
# An (optional) login password for the relay server
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 )
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
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 && \
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 && \
(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:
```
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
@ -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
```
### `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`
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`
If your application is sending email from just a few domains (and most application do), it is a good practice to lock the
POSTFIX further down and accept email from these domains only.
Due to in-built spam protection in [Postfix](http://www.postfix.org/postconf.5.html#smtpd_relay_restrictions) you will need to specify
sender domains -- the domains you are using to send your emails from, otherwise Postfix will refuse to start.
Example:
```
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
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.
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.
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
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
echo "******************************"
echo "**** POSTFIX STARTING UP *****"
echo "******************************"
reset=""
yellow=""
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
mkdir -p /var/spool/postfix/ && mkdir -p /var/spool/postfix/pid
@ -20,41 +79,58 @@ postconf -e mydestination=
# Don't relay for any domains
postconf -e relay_domains=
# As this is a server-based service, allow any message size -- we hope the sender knows
# what he is doing
postconf -e "message_size_limit=0"
if [ ! -z "$MESSAGE_SIZE_LIMIT" ]; then
echo -e "$notice Restricting message_size_limit to: ${emphasis}$MESSAGE_SIZE_LIMIT bytes${reset}"
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
postconf -e smtpd_delay_reject=yes
postconf -e smtpd_helo_required=yes
postconf -e "smtpd_helo_restrictions=permit_mynetworks,reject_invalid_helo_hostname,permit"
postconf -e "smtpd_sender_restrictions=permit_mynetworks"
# Set up host name
if [ ! -z "$HOSTNAME" ]; then
echo -e "$notice Setting myhostname: ${emphasis}$HOSTNAME${reset}"
postconf -e myhostname="$HOSTNAME"
else
postconf -# myhostname
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
if [ ! -z "$RELAYHOST" ]; then
echo -n "- Forwarding all emails to $RELAYHOST"
postconf -e relayhost=$RELAYHOST
echo -en "$notice Forwarding all emails to ${emphasis}$RELAYHOST${reset}"
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
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
postmap hash:/etc/postfix/sasl_passwd
postconf -e "smtp_sasl_auth_enable=yes"
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"
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
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 -# smtp_sasl_auth_enable
postconf -# smtp_sasl_password_maps
@ -62,19 +138,27 @@ else
fi
if [ ! -z "$MYNETWORKS" ]; then
postconf -e mynetworks=$MYNETWORKS
echo -e "$notice Using custom allowed networks: ${emphasis}$MYNETWORKS${reset}"
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
# Split with space
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
rm -f $allowed_senders $allowed_senders.db > /dev/null
touch $allowed_senders
for i in $ALLOWED_SENDER_DOMAINS; do
echo -n " $i"
echo -ne " ${emphasis}$i${reset}"
echo -e "$i\tOK" >> $allowed_senders
done
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.
# 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"
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.
postconf -e "smtpd_relay_restrictions=permit"
# Since we are behind closed doors, let's just permit all relays.
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)
sed -i -r -e 's/^#submission/submission/' /etc/postfix/master.cf
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
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" ;;
esac
done
fi
echo "- Staring rsyslog and postfix"
echo -e "$notice Staring ${emphasis}rsyslog${reset} and ${emphasis}postfix${reset}"
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