Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to debian 12 and add postfix_exporter #49

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 24 additions & 31 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
FROM golang:1.16 as postfix_exporter

ENV \
POSTFIX_EXPORTER_VERSION=0.3.0 \
POSTFIX_EXPORTER_CHECKSUM=a0d45f3615d6f24b5532d4048fbb08a248588cac7587279aef1473b6e50b6157

RUN set -x \
&& wget "https://github.com/kumina/postfix_exporter/archive/refs/tags/${POSTFIX_EXPORTER_VERSION}.tar.gz" \
&& echo "${POSTFIX_EXPORTER_CHECKSUM} ${POSTFIX_EXPORTER_VERSION}.tar.gz" > SHA256SUM \
&& ( sha256sum -c SHA256SUM || ( echo "Expected ${POSTFIX_EXPORTER_VERSION}.tar.gz: $(sha256sum ${POSTFIX_EXPORTER_VERSION}.tar.gz)"; exit 1; )) \
&& tar -zxf ${POSTFIX_EXPORTER_VERSION}.tar.gz \
&& cd postfix_exporter-${POSTFIX_EXPORTER_VERSION} \
&& go mod download \
&& go install -tags nosystemd,nodocker \
;

# Postfix SMTP Relay

FROM debian:bullseye
# Debian Bookworm
FROM debian:12

EXPOSE 25 587 2525

Expand All @@ -16,39 +33,11 @@ RUN set -x \
RUN set -x \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y --no-install-recommends postfix mailutils busybox-syslogd opendkim opendkim-tools libsasl2-modules sasl2-bin curl ca-certificates procps \
&& apt-get install -y --no-install-recommends postfix mailutils busybox-syslogd opendkim opendkim-tools libsasl2-modules sasl2-bin curl ca-certificates procps s6 inotify-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
;

# Install s6
RUN set -x \
&& S6_VERSION=2.11.0.0 \
&& EXECLINE_VERSION=2.8.1.0 \
&& SKAWARE_RELEASE=2.0.7 \
&& S6_CHECKSUM_X86_64=fcf79204c1957016fc88b0ad7d98f150071483583552103d5822cbf56824cc87 \
&& S6_CHECKSUM_AARCH64=64151e136f887c6c2c7df69e3100573c318ec7400296680cc698bc7b0ca36943 \
&& EXECLINE_CHECKSUM_X86_64=b216cfc4db928729d950df5a354aa34bc529e8250b55ab0de700193693dea682 \
&& EXECLINE_CHECKSUM_AARCH64=8cb1d5c2d44cb94990d63023db48f7d3cd71ead10cbb19c05b99dbd528af5748 \
&& if [ "$(uname -m)" = "x86_64" ] ; then \
S6_CHECKSUM="${S6_CHECKSUM_X86_64}"; \
EXECLINE_CHECKSUM="${EXECLINE_CHECKSUM_X86_64}"; \
SKAWARE_ARCH="amd64"; \
elif [ "$(uname -m)" = "aarch64" ]; then \
S6_CHECKSUM="${S6_CHECKSUM_AARCH64}"; \
EXECLINE_CHECKSUM="${EXECLINE_CHECKSUM_AARCH64}"; \
SKAWARE_ARCH="aarch64"; \
fi \
&& curl -sSf -L -o /tmp/s6-${S6_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz https://github.com/just-containers/skaware/releases/download/v${SKAWARE_RELEASE}/s6-${S6_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz \
&& curl -sSf -L -o /tmp/execline-${EXECLINE_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz https://github.com/just-containers/skaware/releases/download/v${SKAWARE_RELEASE}/execline-${EXECLINE_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz \
&& echo "${S6_CHECKSUM} s6-${S6_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz" > /tmp/SHA256SUM \
&& echo "${EXECLINE_CHECKSUM} execline-${EXECLINE_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz" >> /tmp/SHA256SUM \
&& ( cd /tmp; sha256sum -c SHA256SUM || ( echo "Expected S6: $(sha256sum s6-${S6_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz) Execline: $(sha256sum execline-${EXECLINE_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz)"; exit 1; )) \
&& tar -C /usr -zxf /tmp/s6-${S6_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz \
&& tar -C /usr -zxf /tmp/execline-${EXECLINE_VERSION}-linux-${SKAWARE_ARCH}-bin.tar.gz \
&& rm -rf /tmp/* \
;

# Configure Postfix / dkim
RUN set -x \
&& postconf -e smtpd_banner="\$myhostname ESMTP" \
Expand All @@ -63,9 +52,13 @@ RUN set -x \

COPY header_checks /etc/postfix/header_checks
COPY opendkim.conf.sh /etc/

COPY --from=postfix_exporter /go/bin/postfix_exporter /usr/local/bin/postfix_exporter
COPY s6 /etc/s6/
COPY entry.sh /

RUN set -x \
&& chmod 0644 /etc/postfix/header_checks \
;

ENTRYPOINT ["/entry.sh"]
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
39 changes: 25 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,45 @@ NAME := postfix
TAG := latest
IMAGE_NAME := panubo/$(NAME)

.PHONY: *
.PHONY: help bash run run-* build push clean _ci_test

help:
@printf "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)\n"

bash: ## Runs a bash shell in the docker image
docker run --rm -it -e MAILNAME=mail.example.com $(IMAGE_NAME):latest bash
docker run --rm -it -e MAILNAME=mail.example.com $(IMAGE_NAME):$(TAG) bash

run: ## Runs the docker image in a test mode
$(eval ID := $(shell docker run -d --name postfix -e RELAYHOST=172.17.0.2 -e MAILNAME=mail.example.com -e SIZELIMIT=20480000 -e LOGOUTPUT=/var/log/maillog $(IMAGE_NAME):latest))
$(eval ID := $(shell docker rm -f $(NAME) >/dev/null 2>&1; docker run -d --name $(NAME) --hostname mail.example.com \
-e RELAYHOST=172.17.0.2 \
-e MAILNAME=mail.example.com \
-e SIZELIMIT=20480000 \
-e LOGOUTPUT=/var/log/maillog \
-e CONFIG_RELOADER_ENABLED=true \
-e POSTFIX_EXPORTER_ENABLED=false $(IMAGE_NAME):$(TAG)))
$(eval IP := $(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${ID}))
@echo "Running ${ID} @ smtp://${IP}"
@docker attach ${ID}
@docker kill ${ID}

run-tls: ## Runs the docker image in a test mode with TLS
$(eval ID := $(shell docker run -d --name postfix --hostname mail.example.com -e RELAYHOST=172.17.0.2 -e MAILNAME=mail.example.com -e USE_TLS=yes $(IMAGE_NAME):latest))
$(eval IP := $(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${ID}))
@echo "Running ${ID} @ smtp://${IP}"
@docker attach ${ID}
@docker kill ${ID}

run-dkim: ## Runs the docker image in a test mode with DKIM
$(eval ID := $(shell docker run -d --name postfix --hostname mail.example.com -e RELAYHOST=172.17.0.2 -e MAILNAME=mail.example.com -e DKIM_DOMAINS=foo.example.com,bar.example.com,example.net -e USE_DKIM=yes -v `pwd`/dkim.key:/etc/opendkim/dkim.key $(IMAGE_NAME):latest))
run-dkim: dkim.key ## Runs the docker image in a test mode with DKIM
$(eval ID := $(shell docker rm -f $(NAME) >/dev/null 2>&1; docker run -d --name $(NAME) --hostname mail.example.com \
-e RELAYHOST=172.17.0.2 \
-e MAILNAME=mail.example.com \
-e CONFIG_RELOADER_ENABLED=true \
-e USE_DKIM=yes -v `pwd`/dkim.key:/etc/opendkim/dkim.key $(IMAGE_NAME):$(TAG)))
$(eval IP := $(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${ID}))
@echo "Running ${ID} @ smtp://${IP}"
@docker attach ${ID}
@docker kill ${ID}

run-all-dkim: ## Runs the docker image in a test mode. All settings
$(eval ID := $(shell docker run -d --name postfix --hostname mail.example.com -e RELAYHOST=172.17.0.2 -e MAILNAME=mail.example.com -e DKIM_DOMAINS=foo.example.com,bar.example.com,example.net -e DKIM_SELECTOR=6091aa68-f43d-47cf-a52e-bafda525d0bc -e USE_DKIM=yes -v `pwd`/dkim.key:/etc/opendkim/dkim.key $(IMAGE_NAME):latest))
run-all-dkim: dkim.key ## Runs the docker image in a test mode. All settings
$(eval ID := $(shell docker rm -f $(NAME) >/dev/null 2>&1; docker run -d --name $(NAME) --hostname mail.example.com \
-e RELAYHOST=172.17.0.2 \
-e MAILNAME=mail.example.com \
-e DKIM_DOMAINS=foo.example.com,bar.example.com,example.net \
-e DKIM_SELECTOR=6091aa68-f43d-47cf-a52e-bafda525d0bc \
-e USE_DKIM=yes -v `pwd`/dkim.key:/etc/opendkim/dkim.key $(IMAGE_NAME):$(TAG)))
$(eval IP := $(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${ID}))
@echo "Running ${ID} @ smtp://${IP}"
@docker attach ${ID}
Expand All @@ -49,3 +57,6 @@ clean: ## Remove built image

_ci_test:
true

dkim.key:
openssl genrsa -out dkim.key 2048
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ This image is available on quay.io `quay.io/panubo/postfix` and AWS ECR Public `

- `MAILNAME` - set this to a legitimate FQDN hostname for this service (required). (example, `mail.example.com`)
- `MYNETWORKS` - comma separated list of IP subnets that are allowed to relay. Default `127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16`
- `LOGOUTPUT` - Syslog log file location. eg `/var/log/maillog`. Default `/dev/stdout`.
- `LOGOUTPUT` - Log file location. eg `/var/log/maillog`. Default `/dev/stdout`. See [Logging](#logging)
- `TZ` - set timezone. This is used by Postfix to create `Received` headers. Default `UTC`.
- `POSTFIX_EXPORTER_ENABLED` - enable the Prometheus postfix_exporter. Default `false`. See [Postfix Exporter](#postfix-prometheus-exporter)

**General Postfix:**

Expand Down Expand Up @@ -89,6 +90,32 @@ POSTCONF=masquerade_domains=foo.example.com example.com;masquerade_exceptions=ro

Would result in `masquerade_domains` and `masquerade_exceptions` being configured for Postfix.

**Config Reloader**

The config reloader watches the known TLS cert and keys (`TLS_CRT`, `TLS_KEY` etc) for changes (`mv` or updated Kubernetes secret) then reloads Postfix.

- `CONFIG_RELOADER_ENABLED` - Enable the config reloader. Default `false`, must be set to `true` to enable.

## Postfix Prometheus Exporter

This image comes with [kumina/postfix_exporter](https://github.com/kumina/postfix_exporter) pre-installed. To enable set the environment variable `POSTFIX_EXPORTER_ENABLED=true` (this must be exactly "true"). The exporter requires that the logoutput is `/dev/stdout` it can't be anything else.

The exporter listens on port `9154/tcp`.

See [Logging](#logging)

## Logging

This container outputs the Postfix mail log to stdout by default, additionally logs are saved to `/var/log/s6-maillog/current` which is rotated every 10MB with only 3 log files retained.

If you want to output somewhere else you can set environment variable `LOGOUTPUT`. For example `LOGOUTPUT=/var/log/maillog`.

When enabled OpenDKIM only supports syslog output, the syslogd daemon is only used for OpenDKIM. Only /dev/stdout is supported for OpenDKIM syslog logs.

_Note: the Postfix Prometheus exporter only works when the logs are left at /dev/stdout. This requirement of logs going to /dev/stdout is due to the containers logging structure. This may be improved but was needed to keep with backwards compatibility without adding additional variables to configured_

_Note: The log `/var/log/s6-maillog/current` is always created but won't actually contain any logs if `LOGOUTPUT` is not `/dev/stdout`._

## Custom Scripts

Executable shell scripts and binaries can be mounted or copied in to `/etc/entrypoint.d`. These will be run when the container is launched but before postfix is started. These can be used to customise the behaviour of the container.
Expand Down
8 changes: 8 additions & 0 deletions s6/config-reloader/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

: "${CONFIG_RELOADER_ENABLED:=false}"

if [[ "${CONFIG_RELOADER_ENABLED}" == "true" ]]; then
# Shutdown everything and exit the process crashes or is stopped.
s6-svscanctl -t /etc/s6
fi
28 changes: 28 additions & 0 deletions s6/config-reloader/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash

# This script watches the known TLS cert and keys for changes (`mv` or updated Kubernetes secret) then reloads postfix.

: "${CONFIG_RELOADER_ENABLED:=false}"

if [[ "${CONFIG_RELOADER_ENABLED}" != "true" ]]; then
echo "config-reloader >> Config reloader is not being started"
s6-svc -d "$(pwd)"
exit
fi

watch_files=(
"${TLS_CRT:-/etc/ssl/certs/ssl-cert-snakeoil.pem}"
"${TLS_KEY:-/etc/ssl/private/ssl-cert-snakeoil.key}"
"${CLIENT_TLS_KEY:-/etc/ssl/certs/ssl-cert-snakeoil.pem}"
"${CLIENT_TLS_CRT:-/etc/ssl/private/ssl-cert-snakeoil.key}"
)

# Start infinite loop
while true; do
postfix reload
echo "config-reloader >> Waiting on config changes..."
# delete_self is the event that triggers when the link is removed and replaced.
inotifywait --event delete_self "${watch_files[@]}"
# sleep to prevent race condition
sleep 3
done
6 changes: 6 additions & 0 deletions s6/postfix/log/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

mkdir /var/log/s6-maillog
chown postfix:postfix /var/log/s6-maillog

exec s6-setuidgid postfix s6-log -bp 1 n3 s10000000 /var/log/s6-maillog
9 changes: 7 additions & 2 deletions s6/postfix/run
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set -e
# Defaults
: "${SIZELIMIT:=15728640}" # 10Meg with headroom
: "${RELAYHOST:=}" # empty
: "${LOGOUTPUT:=/dev/stdout}"

# TLS
: "${USE_TLS:=yes}"
Expand Down Expand Up @@ -219,14 +220,18 @@ if [ ! -z "${SMTPD_USERS}" ]; then
sed -i -E 's/etc\/host\.conf etc\/nsswitch\.conf etc\/nss_mdns\.config"/etc\/host.conf etc\/nsswitch.conf etc\/nss_mdns.config etc\/sasldb2"/' /usr/lib/postfix/configure-instance.sh
fi

# Configure logging
echo "postfix >> Setting maillog_file to ${LOGOUTPUT}"
postconf -e maillog_file="${LOGOUTPUT}"

# Configure advanced settings
if [ -n "${POSTCONF}" ]; then
echo "postfix >> Configuring additional postfix parameters"
# Note: Used ; as IFS since comma is common in postfix options
IFS=';' read -ra CONFIG <<< "${POSTCONF}"
for C in "${CONFIG[@]}"; do
IFS='=' read -ra MAP <<< "$C"
echo "postfix >> Setting parameter ${MAP[0]}"
echo "postfix >> Setting parameter ${MAP[0]} to ${MAP[1]}"
postconf -e "$C"
done
fi
Expand All @@ -241,5 +246,5 @@ rm -f /var/spool/postfix/pid/*
echo "postfix >> Checking Postfix Configuration"
postfix check

# start postfix in foreground
echo "postfix >> Starting postfix"
exec /usr/sbin/postfix start-fg
6 changes: 6 additions & 0 deletions s6/postfix_exporter/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

if [[ "${POSTFIX_EXPORTER_ENABLED}" == "true" ]]; then
# Shutdown everything and exit the process crashes or is stopped.
s6-svscanctl -t /etc/s6
fi
23 changes: 23 additions & 0 deletions s6/postfix_exporter/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash

set -e

[ "$DEBUG" == 'true' ] && set -x

# Defaults
: "${LOGOUTPUT:=/dev/stdout}"

if [[ "${POSTFIX_EXPORTER_ENABLED}" == "true" ]] && [[ "${LOGOUTPUT}" != "/dev/stdout" ]]; then
echo "postfix_exporter >> FATAL exporter is enabled but requires LOGOUTPUT is set to /dev/stdout"
s6-svscanctl -t /etc/s6
exit 1
elif [[ "${POSTFIX_EXPORTER_ENABLED}" == "true" ]]; then
s6-svwait -u /etc/s6/postfix/log
echo "postfix_exporter >> Starting postfix_exporter"
exec s6-setuidgid postfix postfix_exporter --postfix.logfile_path=/var/log/s6-maillog/current
fi

echo "postfix_exporter >> POSTFIX_EXPORTER_ENABLED not \"true\", not starting postfix_exporter"

s6-svc -d "$(pwd)"
exit
7 changes: 2 additions & 5 deletions s6/syslogd/run
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ set -e

[ "$DEBUG" == 'true' ] && set -x

# Defaults
: "${LOGOUTPUT:=/dev/stdout}"
echo "syslogd >> Setting syslogd output to /dev/stdout (Only used for OpenDKIM)"

echo "syslogd >> Setting syslogd output to ${LOGOUTPUT}"

exec syslogd -n -O "${LOGOUTPUT}" -S
exec syslogd -n -O "/dev/stdout" -S