diff --git a/data/Dockerfiles/postfix-tlspol/Dockerfile b/data/Dockerfiles/postfix-tlspol/Dockerfile new file mode 100644 index 000000000..95a035265 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/Dockerfile @@ -0,0 +1,49 @@ +FROM golang:1.25-bookworm AS builder +WORKDIR /src + +ENV CGO_ENABLED=0 \ + GO111MODULE=on \ + VERSION=1.8.14 + +RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \ + cd /src/postfix-tlspol && \ + scripts/build.sh build-only + + +FROM debian:bookworm-slim +LABEL maintainer="The Infrastructure Company GmbH " + +ARG DEBIAN_FRONTEND=noninteractive +ENV LC_ALL=C + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + dirmngr \ + dnsutils \ + iputils-ping \ + sudo \ + supervisor \ + redis-tools \ + syslog-ng \ + syslog-ng-core \ + syslog-ng-mod-redis \ + tzdata \ + && rm -rf /var/lib/apt/lists/* \ + && touch /etc/default/locale + +COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf +COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf +COPY postfix-tlspol.sh /opt/postfix-tlspol.sh +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh +COPY docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol + +RUN chmod +x /opt/postfix-tlspol.sh \ + /usr/local/sbin/stop-supervisor.sh \ + /docker-entrypoint.sh +RUN rm -rf /tmp/* /var/tmp/* + +ENTRYPOINT ["/docker-entrypoint.sh"] + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh b/data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh new file mode 100755 index 000000000..8c4f2c43e --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf +fi + +exec "$@" \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh b/data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh new file mode 100755 index 000000000..407a08f6f --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +LOGLVL=info + +if [ ${DEV_MODE} != "n" ]; then + echo -e "\e[31mEnabling debug mode\e[0m" + set -x + LOGLVL=debug +fi + +[[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol +[[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol + +until dig +short mailcow.email > /dev/null; do + echo "Waiting for DNS..." + sleep 1 +done + +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi + +until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do + echo "Waiting for Redis..." + sleep 2 +done + +echo "Waiting for Postfix..." +until ping postfix -c1 > /dev/null; do + sleep 1 +done +echo "Postfix OK" + +cat < /etc/postfix-tlspol/config.yaml +server: + address: 0.0.0.0:8642 + + log-level: ${LOGLVL} + + prefetch: true + + cache-file: /var/lib/postfix-tlspol/cache.db + +dns: + # must support DNSSEC + address: 127.0.0.11:53 +EOF + +/usr/local/bin/postfix-tlspol -config /etc/postfix-tlspol/config.yaml \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/stop-supervisor.sh b/data/Dockerfiles/postfix-tlspol/stop-supervisor.sh new file mode 100755 index 000000000..5394490ce --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/postfix-tlspol/supervisord.conf b/data/Dockerfiles/postfix-tlspol/supervisord.conf new file mode 100644 index 000000000..90cf785ad --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/supervisord.conf @@ -0,0 +1,25 @@ +[supervisord] +pidfile=/var/run/supervisord.pid +nodaemon=true +user=root + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true + +[program:postfix-tlspol] +startsecs=10 +autorestart=true +command=/opt/postfix-tlspol.sh +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf b/data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf new file mode 100644 index 000000000..3862a3547 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf @@ -0,0 +1,45 @@ +@version: 3.38 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + dns_cache(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats_freq(0); + bad_hostname("^gconfd$"); +}; +source s_src { + unix-stream("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis1") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +filter f_mail { facility(mail); }; +# start +# overriding warnings are still displayed when the entrypoint runs its initial check +# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs +# Some other warnings are ignored +filter f_ignore { + not match("overriding earlier entry" value("MESSAGE")); + not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); + not match("no SASL support" value("MESSAGE")); + not facility (local0, local1, local2, local3, local4, local5, local6, local7); +}; +# end +log { + source(s_src); + filter(f_ignore); + destination(d_stdout); + filter(f_mail); + destination(d_redis_ui_log); +}; diff --git a/data/Dockerfiles/postfix-tlspol/syslog-ng.conf b/data/Dockerfiles/postfix-tlspol/syslog-ng.conf new file mode 100644 index 000000000..7126c1250 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/syslog-ng.conf @@ -0,0 +1,45 @@ +@version: 3.38 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + dns_cache(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats_freq(0); + bad_hostname("^gconfd$"); +}; +source s_src { + unix-stream("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("redis-mailcow") + persist-name("redis1") + port(6379) + auth("`REDISPASS`") + command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +filter f_mail { facility(mail); }; +# start +# overriding warnings are still displayed when the entrypoint runs its initial check +# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs +# Some other warnings are ignored +filter f_ignore { + not match("overriding earlier entry" value("MESSAGE")); + not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); + not match("no SASL support" value("MESSAGE")); + not facility (local0, local1, local2, local3, local4, local5, local6, local7); +}; +# end +log { + source(s_src); + filter(f_ignore); + destination(d_stdout); + filter(f_mail); + destination(d_redis_ui_log); +}; diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile index d1a32917d..994612ec4 100644 --- a/data/Dockerfiles/postfix/Dockerfile +++ b/data/Dockerfiles/postfix/Dockerfile @@ -1,13 +1,3 @@ -FROM golang:1.25-bookworm AS builder -WORKDIR /src - -ENV CGO_ENABLED=0 \ - GO111MODULE=on - -RUN git clone https://github.com/Zuplu/postfix-tlspol.git && \ - cd postfix-tlspol && \ - scripts/build.sh build-only - FROM debian:bookworm-slim LABEL maintainer="The Infrastructure Company GmbH " @@ -58,7 +48,6 @@ COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY docker-entrypoint.sh /docker-entrypoint.sh -COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol RUN chmod +x /opt/postfix.sh \ /usr/local/bin/rspamd-pipe-ham \ diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 44b7d7d39..0a6494ed6 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -3,9 +3,6 @@ trap "postfix stop" EXIT [[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/ -[[ ! -d /opt/postfix/conf/postfix-tlspol ]] && mkdir -p /opt/postfix/conf/postfix-tlspol -[[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol -[[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol # Wait for MySQL to warm-up while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do @@ -506,26 +503,6 @@ if [[ ! -f /opt/postfix/conf/custom_postscreen_whitelist.cidr ]]; then EOF fi -cat < /opt/postfix/conf/postfix-tlspol/config.yaml -server: - address: 127.0.0.1:8642 - - log-level: info - - prefetch: true - - cache-file: /var/lib/postfix-tlspol/cache.db - -dns: - # must support DNSSEC - address: 127.0.0.11:53 -EOF - -# Fixing local command execution of postfix-tlspol with symlink to config -if [ ! -L /etc/postfix-tlspol/config.yaml ]; then - ln -s /opt/postfix/conf/postfix-tlspol/config.yaml /etc/postfix-tlspol/config.yaml -fi - # Fix Postfix permissions chown -R root:postfix /opt/postfix/conf/sql/ /opt/postfix/conf/custom_transport.pcre chmod 640 /opt/postfix/conf/sql/*.cf /opt/postfix/conf/custom_transport.pcre diff --git a/data/Dockerfiles/postfix/supervisord.conf b/data/Dockerfiles/postfix/supervisord.conf index 26fec862c..ba70f8edf 100644 --- a/data/Dockerfiles/postfix/supervisord.conf +++ b/data/Dockerfiles/postfix/supervisord.conf @@ -11,15 +11,6 @@ stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true -[program:postfix-tlspol] -startsecs=10 -autorestart=true -command=/usr/local/bin/postfix-tlspol -config /opt/postfix/conf/postfix-tlspol/config.yaml -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - [program:postfix] command=/opt/postfix.sh stdout_logfile=/dev/stdout diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index e4354c40c..f091cb3f9 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -152,7 +152,7 @@ smtp_sasl_auth_enable = yes smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf smtp_sasl_security_options = smtp_sasl_mechanism_filter = plain, login -smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:127.0.0.1:8642:QUERY +smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre mail_name = Postcow # local_transport map catches local destinations and prevents routing local dests when the next map would route "*" diff --git a/docker-compose.yml b/docker-compose.yml index 5878a8b49..1b47cf592 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -378,6 +378,30 @@ services: aliases: - postfix + postfix-tlspol-mailcow: + image: ghcr.io/mailcow/postfix-tlspol:1.0 + depends_on: + unbound-mailcow: + condition: service_healthy + postfix-mailcow: + condition: service_started + volumes: + - postfix-tlspol-vol-1:/var/lib/postfix-tlspol + environment: + - LOG_LINES=${LOG_LINES:-9999} + - TZ=${TZ} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + - REDISPASS=${REDISPASS} + - DEV_MODE=${DEV_MODE:-n} + restart: always + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + networks: + mailcow-network: + aliases: + - postfix-tlspol + memcached-mailcow: image: memcached:alpine restart: always @@ -649,6 +673,7 @@ volumes: redis-vol-1: rspamd-vol-1: postfix-vol-1: + postfix-tlspol-vol-1: crypt-vol-1: sogo-web-vol-1: sogo-userdata-backup-vol-1: