From 3d7ac66def7ab0b18096a63ffa1c57c8d560cac9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 14:46:10 +0000 Subject: [PATCH 01/11] Refactor: Unbound to use port 53 and NET_BIND_SERVICE Co-authored-by: j.boehm --- deploy/compose/unbound.yaml | 2 ++ deploy/kustomize/mta/statefulset.yaml | 2 ++ deploy/kustomize/unbound/deployment.yaml | 8 ++++++-- docs/UPGRADE.md | 5 +++++ target/unbound/Dockerfile | 4 ++-- target/unbound/rootfs/etc/unbound/unbound.conf | 2 +- test/bats/Dockerfile | 3 +-- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/deploy/compose/unbound.yaml b/deploy/compose/unbound.yaml index ba3a84d3..9df63e3e 100644 --- a/deploy/compose/unbound.yaml +++ b/deploy/compose/unbound.yaml @@ -7,6 +7,8 @@ services: cache_from: - type=registry,ref=ghcr.io/jeboehm/mailserver-unbound:buildcache restart: on-failure:5 + cap_add: + - NET_BIND_SERVICE security_opt: - no-new-privileges read_only: false diff --git a/deploy/kustomize/mta/statefulset.yaml b/deploy/kustomize/mta/statefulset.yaml index 603f018b..73bb13ea 100644 --- a/deploy/kustomize/mta/statefulset.yaml +++ b/deploy/kustomize/mta/statefulset.yaml @@ -135,6 +135,8 @@ spec: # important! required for pod lookup in postfix postconf smtp_host_lookup=native postconf lmtp_host_lookup=native + # ensure no leftover resolver override from earlier versions + rm -f /var/spool/postfix/etc/resolv.conf || true resources: requests: cpu: 50m diff --git a/deploy/kustomize/unbound/deployment.yaml b/deploy/kustomize/unbound/deployment.yaml index d6e9f238..0ef2deaf 100644 --- a/deploy/kustomize/unbound/deployment.yaml +++ b/deploy/kustomize/unbound/deployment.yaml @@ -40,10 +40,10 @@ spec: - ALL ports: - name: dns - containerPort: 5353 + containerPort: 53 protocol: UDP - name: dns-tcp - containerPort: 5353 + containerPort: 53 protocol: TCP livenessProbe: exec: @@ -66,6 +66,10 @@ spec: requests: cpu: 50m memory: 32Mi + securityContext: + capabilities: + add: + - NET_BIND_SERVICE securityContext: fsGroup: 101 fsGroupChangePolicy: OnRootMismatch diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md index b2f8744b..5156dfbe 100644 --- a/docs/UPGRADE.md +++ b/docs/UPGRADE.md @@ -21,6 +21,11 @@ The Helm chart has been deprecated and archived. - **Mail Submission**: Mail submission is now only possible on port 587. ### MDA (Mail Delivery Agent) +- **Unbound port change and capability requirement (breaking)**: Unbound now listens on port `53` (UDP/TCP) instead of `5353`. + - Compose: the `unbound` service now requires `cap_add: [NET_BIND_SERVICE]` to bind <1024 as non-root. + - Kubernetes: the `unbound` deployment exposes containerPorts `53/TCP` and `53/UDP` and adds the `NET_BIND_SERVICE` capability. + - Rspamd and internal components should use `unbound:53`. Any hardcoded `:5353` must be updated. + - If you previously customized Postfix to use `127.0.0.1:5353`, remove that customization. Postfix and other services should resolve via standard port 53. - **Base Image**: Changed to `dovecot/dovecot`. This image is no longer based on Alpine Linux. - **TLS Certificate Paths**: Updated to `/etc/dovecot/tls/tls.crt` and `/etc/dovecot/tls/tls.key`. A Diffie-Hellman file is no longer required. diff --git a/target/unbound/Dockerfile b/target/unbound/Dockerfile index c5a9e229..bb6c729c 100644 --- a/target/unbound/Dockerfile +++ b/target/unbound/Dockerfile @@ -9,5 +9,5 @@ RUN chown -R unbound:unbound /etc/unbound && \ COPY --chown=unbound:unbound rootfs/ / USER unbound -EXPOSE 5353/tcp 5353/udp -HEALTHCHECK CMD /usr/local/bin/healthcheck.sh +EXPOSE 53/TCP 53/UDP +HEALTHCHECK CMD dig @127.0.0.1 -p 53 github.com || exit 1 diff --git a/target/unbound/rootfs/etc/unbound/unbound.conf b/target/unbound/rootfs/etc/unbound/unbound.conf index ad94098c..a412aee6 100644 --- a/target/unbound/rootfs/etc/unbound/unbound.conf +++ b/target/unbound/rootfs/etc/unbound/unbound.conf @@ -1,7 +1,7 @@ server: chroot: "" username: "" - port: 5353 + port: 53 trust-anchor-file: "/etc/unbound/root.key" do-ip6: no diff --git a/test/bats/Dockerfile b/test/bats/Dockerfile index 114028f4..ad822714 100644 --- a/test/bats/Dockerfile +++ b/test/bats/Dockerfile @@ -17,8 +17,7 @@ ENV MYSQL_HOST=db \ MDA_IMAPS_ADDRESS=mda:31993 \ MTA_SMTP_ADDRESS=mta:25 \ MTA_SMTP_SUBMISSION_ADDRESS=mta:587 \ - RELAYHOST=false \ - UNBOUND_DNS_ADDRESS=unbound:5353 \ + UNBOUND_DNS_ADDRESS=unbound:53 \ WAITSTART_TIMEOUT=1m \ WEB_HTTP_ADDRESS=web:8080 From 4042db3a93d25708752543dab90e1398b68bd512 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 14:52:05 +0000 Subject: [PATCH 02/11] Checkpoint before follow-up message Co-authored-by: j.boehm --- deploy/kustomize/mta/statefulset.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/deploy/kustomize/mta/statefulset.yaml b/deploy/kustomize/mta/statefulset.yaml index 73bb13ea..603f018b 100644 --- a/deploy/kustomize/mta/statefulset.yaml +++ b/deploy/kustomize/mta/statefulset.yaml @@ -135,8 +135,6 @@ spec: # important! required for pod lookup in postfix postconf smtp_host_lookup=native postconf lmtp_host_lookup=native - # ensure no leftover resolver override from earlier versions - rm -f /var/spool/postfix/etc/resolv.conf || true resources: requests: cpu: 50m From a95d4fa4df97fb14ddfaac1520d4dca3d7b7bbba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 14:54:29 +0000 Subject: [PATCH 03/11] Refactor: Change unbound port to 53 and update healthcheck Co-authored-by: j.boehm --- deploy/kustomize/unbound/network-policy.yaml | 4 ++-- docs/UPGRADE.md | 14 ++++++++------ target/unbound/Dockerfile | 2 +- .../unbound/rootfs/usr/local/bin/healthcheck.sh | 16 +++++++--------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/deploy/kustomize/unbound/network-policy.yaml b/deploy/kustomize/unbound/network-policy.yaml index 51aac11f..0643225f 100644 --- a/deploy/kustomize/unbound/network-policy.yaml +++ b/deploy/kustomize/unbound/network-policy.yaml @@ -20,6 +20,6 @@ spec: - podSelector: {} ports: - protocol: UDP - port: 5353 # DNS + port: 53 # DNS - protocol: TCP - port: 5353 # DNS TCP + port: 53 # DNS TCP diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md index 5156dfbe..9acc7f4c 100644 --- a/docs/UPGRADE.md +++ b/docs/UPGRADE.md @@ -21,12 +21,6 @@ The Helm chart has been deprecated and archived. - **Mail Submission**: Mail submission is now only possible on port 587. ### MDA (Mail Delivery Agent) -- **Unbound port change and capability requirement (breaking)**: Unbound now listens on port `53` (UDP/TCP) instead of `5353`. - - Compose: the `unbound` service now requires `cap_add: [NET_BIND_SERVICE]` to bind <1024 as non-root. - - Kubernetes: the `unbound` deployment exposes containerPorts `53/TCP` and `53/UDP` and adds the `NET_BIND_SERVICE` capability. - - Rspamd and internal components should use `unbound:53`. Any hardcoded `:5353` must be updated. - - If you previously customized Postfix to use `127.0.0.1:5353`, remove that customization. Postfix and other services should resolve via standard port 53. - - **Base Image**: Changed to `dovecot/dovecot`. This image is no longer based on Alpine Linux. - **TLS Certificate Paths**: Updated to `/etc/dovecot/tls/tls.crt` and `/etc/dovecot/tls/tls.key`. A Diffie-Hellman file is no longer required. - **Mail Storage**: Now mounted to `/srv/vmail` instead of `/var/vmail`. @@ -41,3 +35,11 @@ The Helm chart has been deprecated and archived. - **Full Text Search**: Enabled by default. All `FTS_` environment variables have been removed. - **Protocol Support**: POP3 and IMAP are always enabled. The `POP3_ENABLED` and `IMAP_ENABLED` environment variables have been removed. + +## v6.0 to v6.1 + +- **Unbound port change and capability requirement (breaking)**: Unbound now listens on port `53` (UDP/TCP) instead of `5353`. + - Compose: the `unbound` service now requires `cap_add: [NET_BIND_SERVICE]` to bind <1024 as non-root. + - Kubernetes: the `unbound` deployment exposes containerPorts `53/TCP` and `53/UDP` and adds the `NET_BIND_SERVICE` capability. + - Rspamd and internal components should use `unbound:53`. Any hardcoded `:5353` must be updated. + - If you previously customized Postfix to use `127.0.0.1:5353`, remove that customization. Postfix and other services should resolve via standard port 53. diff --git a/target/unbound/Dockerfile b/target/unbound/Dockerfile index bb6c729c..35213d2e 100644 --- a/target/unbound/Dockerfile +++ b/target/unbound/Dockerfile @@ -10,4 +10,4 @@ COPY --chown=unbound:unbound rootfs/ / USER unbound EXPOSE 53/TCP 53/UDP -HEALTHCHECK CMD dig @127.0.0.1 -p 53 github.com || exit 1 +HEALTHCHECK CMD /usr/local/bin/healthcheck.sh diff --git a/target/unbound/rootfs/usr/local/bin/healthcheck.sh b/target/unbound/rootfs/usr/local/bin/healthcheck.sh index 469c8b20..ff0bcee2 100755 --- a/target/unbound/rootfs/usr/local/bin/healthcheck.sh +++ b/target/unbound/rootfs/usr/local/bin/healthcheck.sh @@ -1,18 +1,16 @@ #!/bin/sh set -e -# Test DNS resolution using dig -if ! dig @127.0.0.1 -p 5353 github.com >/dev/null 2>&1; then - echo "Healthcheck failed: DNS resolution test failed" - exit 1 +# 1. test connection to 0.0.0.0:53 (UDP via dig) and TCP via nc +if ! dig @127.0.0.1 -p 53 github.com +time=2 +tries=1 +short >/dev/null 2>&1; then + echo "Healthcheck failed: dig to 127.0.0.1:53" + exit 1 fi -# Test UDP connectivity (unbound typically uses UDP for DNS) -if ! dig @127.0.0.1 -p 5353 +tcp github.com >/dev/null 2>&1; then - echo "Healthcheck failed: TCP DNS resolution test failed" - exit 1 +if ! nc -z 0.0.0.0 53; then + echo "Healthcheck failed: cannot connect to 0.0.0.0:53 (TCP)" + exit 1 fi echo "Healthcheck passed" - exit 0 From 855ef0622dcd9c4f3c6e4e5e460ada3a4c942d01 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 15:27:12 +0000 Subject: [PATCH 04/11] docs: Update UPGRADE.md for Unbound port change Co-authored-by: j.boehm --- docs/UPGRADE.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md index 9acc7f4c..997fd3f4 100644 --- a/docs/UPGRADE.md +++ b/docs/UPGRADE.md @@ -4,6 +4,14 @@ Upgrade guide for docker-mailserver. Before upgrading, ensure you have updated `docker-compose.yml` and `docker-compose.production.yml` files. +## v6.0 to v6.1 + +- **Unbound port change and capability requirement (breaking)**: Unbound now listens on port `53` (UDP/TCP) instead of `5353`. + - Compose: the `unbound` service now requires `cap_add: [NET_BIND_SERVICE]` to bind <1024 as non-root. + - Kubernetes: the `unbound` deployment exposes containerPorts `53/TCP` and `53/UDP` and adds the `NET_BIND_SERVICE` capability. + - Rspamd and internal components should use `unbound:53`. Any hardcoded `:5353` must be updated. + - If you previously customized Postfix to use `127.0.0.1:5353`, remove that customization. Postfix and other services should resolve via standard port 53. + ## v5.x to v6.0 Deployment on Kubernetes is now a first class citizen. You can use the `kustomization.yaml` file to deploy the mailserver to your Kubernetes cluster. @@ -35,11 +43,3 @@ The Helm chart has been deprecated and archived. - **Full Text Search**: Enabled by default. All `FTS_` environment variables have been removed. - **Protocol Support**: POP3 and IMAP are always enabled. The `POP3_ENABLED` and `IMAP_ENABLED` environment variables have been removed. - -## v6.0 to v6.1 - -- **Unbound port change and capability requirement (breaking)**: Unbound now listens on port `53` (UDP/TCP) instead of `5353`. - - Compose: the `unbound` service now requires `cap_add: [NET_BIND_SERVICE]` to bind <1024 as non-root. - - Kubernetes: the `unbound` deployment exposes containerPorts `53/TCP` and `53/UDP` and adds the `NET_BIND_SERVICE` capability. - - Rspamd and internal components should use `unbound:53`. Any hardcoded `:5353` must be updated. - - If you previously customized Postfix to use `127.0.0.1:5353`, remove that customization. Postfix and other services should resolve via standard port 53. From 98d31346eaf569448eaf1af423488678b4e81714 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 15:57:09 +0000 Subject: [PATCH 05/11] feat: Enable Postfix Postscreen and configure Unbound DNS Co-authored-by: j.boehm --- target/mta/Dockerfile | 22 +++++++++++++++++++++- target/mta/rootfs/usr/local/bin/init.sh | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/target/mta/Dockerfile b/target/mta/Dockerfile index 7de61dbf..0181fe09 100644 --- a/target/mta/Dockerfile +++ b/target/mta/Dockerfile @@ -65,7 +65,27 @@ RUN apk --no-cache add \ postconf smtpd_error_sleep_time=10s && \ postconf smtpd_soft_error_limit=3 && \ postconf smtpd_hard_error_limit=5 && \ - newaliases + newaliases && \ + # enable postscreen on port 25 and supporting services + sed -i 's/^smtp\s\+inet\s\+n\s\+-\s\+y\s\+-\s\+-\s\+smtpd/smtp inet n - y - 1 postscreen/' /etc/postfix/master.cf && \ + printf '%s\n' \ + 'smtpd pass - - y - - smtpd' \ + 'dnsblog unix - - y - 0 dnsblog' \ + 'tlsproxy unix - - y - 0 tlsproxy' \ + >> /etc/postfix/master.cf && \ + postconf postscreen_dnsbl_sites='bl.spamcop.net*2' && \ + postconf postscreen_dnsbl_threshold=2 && \ + postconf postscreen_dnsbl_action=enforce && \ + echo "submission inet n - n - - smtpd" >> /etc/postfix/master.cf && \ + echo " -o syslog_name=postfix/submission" >> /etc/postfix/master.cf && \ + echo " -o smtpd_tls_security_level=encrypt" >> /etc/postfix/master.cf && \ + echo " -o smtpd_sasl_auth_enable=yes" >> /etc/postfix/master.cf && \ + echo " -o smtpd_tls_auth_only=yes" >> /etc/postfix/master.cf && \ + echo " -o smtpd_reject_unlisted_recipient=no" >> /etc/postfix/master.cf && \ + echo " -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject" >> /etc/postfix/master.cf && \ + echo " -o smtpd_relay_restrictions=" >> /etc/postfix/master.cf && \ + echo " -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject" >> /etc/postfix/master.cf && \ + echo " -o milter_macro_daemon_name=ORIGINATING" >> /etc/postfix/master.cf COPY --from=dockerize /bin/dockerize /usr/local/bin/dockerize COPY rootfs/ / diff --git a/target/mta/rootfs/usr/local/bin/init.sh b/target/mta/rootfs/usr/local/bin/init.sh index 905c144e..4c76a352 100755 --- a/target/mta/rootfs/usr/local/bin/init.sh +++ b/target/mta/rootfs/usr/local/bin/init.sh @@ -55,3 +55,23 @@ dockerize \ -template /etc/postfix/mysql-recipient-access.cf.templ:/etc/postfix/mysql-recipient-access.cf \ -template /etc/postfix/mysql-email-submission.cf.templ:/etc/postfix/mysql-email-submission.cf \ /bin/true + +# Configure resolver for Postfix to use $UNBOUND_DNS_ADDRESS +# Accept formats like "host:port" or "ip:port"; default port 53 if omitted +if [ -n "${UNBOUND_DNS_ADDRESS}" ]; then + UNBOUND_DNS_HOST=$(echo "${UNBOUND_DNS_ADDRESS}" | cut -d: -f1) + UNBOUND_DNS_PORT=$(echo "${UNBOUND_DNS_ADDRESS}" | cut -s -d: -f2) + if [ -z "${UNBOUND_DNS_PORT}" ]; then + UNBOUND_DNS_PORT=53 + fi + + # Resolve hostname to IP if necessary + UNBOUND_DNS_IP=$(getent hosts "${UNBOUND_DNS_HOST}" | awk '{print $1}' | head -n1) + if [ -z "${UNBOUND_DNS_IP}" ]; then + UNBOUND_DNS_IP=${UNBOUND_DNS_HOST} + fi + + mkdir -p /var/spool/postfix/etc + echo "nameserver ${UNBOUND_DNS_IP}" > /var/spool/postfix/etc/resolv.conf + # glibc resolv.conf does not support custom port; rely on Unbound standard port 53 +fi From a8f805ca3f3c1c2e3df4ac3086d9530ab552ce0e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 22:09:31 +0000 Subject: [PATCH 06/11] Add NET_BIND_SERVICE capability to unbound container Co-authored-by: j.boehm --- deploy/kustomize/unbound/deployment.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/deploy/kustomize/unbound/deployment.yaml b/deploy/kustomize/unbound/deployment.yaml index 0ef2deaf..3f3a16e7 100644 --- a/deploy/kustomize/unbound/deployment.yaml +++ b/deploy/kustomize/unbound/deployment.yaml @@ -38,6 +38,8 @@ spec: capabilities: drop: - ALL + add: + - NET_BIND_SERVICE ports: - name: dns containerPort: 53 @@ -66,10 +68,6 @@ spec: requests: cpu: 50m memory: 32Mi - securityContext: - capabilities: - add: - - NET_BIND_SERVICE securityContext: fsGroup: 101 fsGroupChangePolicy: OnRootMismatch From 989e59b613f8bd468a152af67bd7f3565bf9c313 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 22:11:38 +0000 Subject: [PATCH 07/11] Refactor healthcheck script to use dig for TCP checks Co-authored-by: j.boehm --- target/unbound/rootfs/usr/local/bin/healthcheck.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/target/unbound/rootfs/usr/local/bin/healthcheck.sh b/target/unbound/rootfs/usr/local/bin/healthcheck.sh index ff0bcee2..e2d54052 100755 --- a/target/unbound/rootfs/usr/local/bin/healthcheck.sh +++ b/target/unbound/rootfs/usr/local/bin/healthcheck.sh @@ -1,14 +1,15 @@ #!/bin/sh set -e -# 1. test connection to 0.0.0.0:53 (UDP via dig) and TCP via nc +# UDP check if ! dig @127.0.0.1 -p 53 github.com +time=2 +tries=1 +short >/dev/null 2>&1; then - echo "Healthcheck failed: dig to 127.0.0.1:53" + echo "Healthcheck failed: dig UDP to 127.0.0.1:53" exit 1 fi -if ! nc -z 0.0.0.0 53; then - echo "Healthcheck failed: cannot connect to 0.0.0.0:53 (TCP)" +# TCP check (no nc dependency) +if ! dig +tcp @127.0.0.1 -p 53 github.com +time=2 +tries=1 +short >/dev/null 2>&1; then + echo "Healthcheck failed: dig TCP to 127.0.0.1:53" exit 1 fi From 9acad5b82f0653943d32ffb2a2176e3383252f44 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Oct 2025 16:46:13 +0000 Subject: [PATCH 08/11] Checkpoint before follow-up message Co-authored-by: j.boehm --- target/mta/Dockerfile | 19 +------------------ target/mta/rootfs/etc/postfix/master.cf | 6 ++++-- target/unbound/Dockerfile | 2 +- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/target/mta/Dockerfile b/target/mta/Dockerfile index 0181fe09..f5082420 100644 --- a/target/mta/Dockerfile +++ b/target/mta/Dockerfile @@ -65,27 +65,10 @@ RUN apk --no-cache add \ postconf smtpd_error_sleep_time=10s && \ postconf smtpd_soft_error_limit=3 && \ postconf smtpd_hard_error_limit=5 && \ - newaliases && \ - # enable postscreen on port 25 and supporting services - sed -i 's/^smtp\s\+inet\s\+n\s\+-\s\+y\s\+-\s\+-\s\+smtpd/smtp inet n - y - 1 postscreen/' /etc/postfix/master.cf && \ - printf '%s\n' \ - 'smtpd pass - - y - - smtpd' \ - 'dnsblog unix - - y - 0 dnsblog' \ - 'tlsproxy unix - - y - 0 tlsproxy' \ - >> /etc/postfix/master.cf && \ postconf postscreen_dnsbl_sites='bl.spamcop.net*2' && \ postconf postscreen_dnsbl_threshold=2 && \ postconf postscreen_dnsbl_action=enforce && \ - echo "submission inet n - n - - smtpd" >> /etc/postfix/master.cf && \ - echo " -o syslog_name=postfix/submission" >> /etc/postfix/master.cf && \ - echo " -o smtpd_tls_security_level=encrypt" >> /etc/postfix/master.cf && \ - echo " -o smtpd_sasl_auth_enable=yes" >> /etc/postfix/master.cf && \ - echo " -o smtpd_tls_auth_only=yes" >> /etc/postfix/master.cf && \ - echo " -o smtpd_reject_unlisted_recipient=no" >> /etc/postfix/master.cf && \ - echo " -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject" >> /etc/postfix/master.cf && \ - echo " -o smtpd_relay_restrictions=" >> /etc/postfix/master.cf && \ - echo " -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject" >> /etc/postfix/master.cf && \ - echo " -o milter_macro_daemon_name=ORIGINATING" >> /etc/postfix/master.cf + newaliases COPY --from=dockerize /bin/dockerize /usr/local/bin/dockerize COPY rootfs/ / diff --git a/target/mta/rootfs/etc/postfix/master.cf b/target/mta/rootfs/etc/postfix/master.cf index b390f449..2ebe8a77 100644 --- a/target/mta/rootfs/etc/postfix/master.cf +++ b/target/mta/rootfs/etc/postfix/master.cf @@ -1,5 +1,7 @@ -smtp inet n - n - - smtpd -#smtp inet n - n - 1 postscreen +smtp inet n - y - 1 postscreen +smtpd pass - - y - - smtpd +dnsblog unix - - y - 0 dnsblog +tlsproxy unix - - y - 0 tlsproxy pickup unix n - n 60 1 pickup cleanup unix n - n - 0 cleanup qmgr unix n - n 300 1 qmgr diff --git a/target/unbound/Dockerfile b/target/unbound/Dockerfile index 35213d2e..5cfa3084 100644 --- a/target/unbound/Dockerfile +++ b/target/unbound/Dockerfile @@ -9,5 +9,5 @@ RUN chown -R unbound:unbound /etc/unbound && \ COPY --chown=unbound:unbound rootfs/ / USER unbound -EXPOSE 53/TCP 53/UDP +EXPOSE 53/tcp 53/udp HEALTHCHECK CMD /usr/local/bin/healthcheck.sh From 1849d5baf3833258b12e37c1cbea46b0d3e43ce6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Oct 2025 16:59:46 +0000 Subject: [PATCH 09/11] fix: grant NET_BIND_SERVICE capability to unbound binary The NET_BIND_SERVICE capability must be set on the unbound binary itself when running as non-root user. Added setcap to grant the capability to /usr/sbin/unbound, allowing it to bind to privileged port 53. --- target/unbound/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/unbound/Dockerfile b/target/unbound/Dockerfile index 5cfa3084..3d171240 100644 --- a/target/unbound/Dockerfile +++ b/target/unbound/Dockerfile @@ -5,7 +5,8 @@ LABEL vendor="https://github.com/jeboehm/docker-mailserver" LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true" RUN chown -R unbound:unbound /etc/unbound && \ - apk add --no-cache bind-tools + apk add --no-cache bind-tools libcap && \ + setcap 'cap_net_bind_service=+ep' /usr/sbin/unbound COPY --chown=unbound:unbound rootfs/ / USER unbound From 07a7e1035c79d818052eafacffa76b88159d28cd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Oct 2025 17:09:02 +0000 Subject: [PATCH 10/11] fix: enable privilege escalation for NET_BIND_SERVICE capability The allowPrivilegeEscalation must be set to true for file capabilities to work in Kubernetes. Without this, even though NET_BIND_SERVICE is added as a container capability and set on the unbound binary with setcap, the process cannot use it. Also removed 'drop: ALL' as it's redundant - we only need to add the specific capability we require. --- deploy/kustomize/unbound/deployment.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deploy/kustomize/unbound/deployment.yaml b/deploy/kustomize/unbound/deployment.yaml index 3f3a16e7..61d24740 100644 --- a/deploy/kustomize/unbound/deployment.yaml +++ b/deploy/kustomize/unbound/deployment.yaml @@ -32,12 +32,10 @@ spec: - secretRef: name: secret-config-env securityContext: - allowPrivilegeEscalation: false + allowPrivilegeEscalation: true runAsUser: 100 runAsGroup: 101 capabilities: - drop: - - ALL add: - NET_BIND_SERVICE ports: From bf3d19d73e869a95d7d287a8c3243ea9d8d2db22 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Oct 2025 17:20:29 +0000 Subject: [PATCH 11/11] refactor: simplify unbound to run as root for port 53 binding Running DNS servers as root is the standard approach for binding to privileged port 53. The previous capability-based approach was overly complex and unreliable across different Kubernetes configurations. Changes: - Removed USER directive from Dockerfile - container runs as root - Removed setcap complexity and libcap dependency - Updated Kubernetes securityContext to allow root but restrict capabilities - Added only necessary capabilities (NET_BIND_SERVICE, CHOWN, SETUID, SETGID) - Kept seccomp profile for additional security This is a more pragmatic and reliable solution that follows industry standard practices for DNS servers. --- deploy/kustomize/unbound/deployment.yaml | 11 +++++------ target/unbound/Dockerfile | 7 ++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/deploy/kustomize/unbound/deployment.yaml b/deploy/kustomize/unbound/deployment.yaml index 61d24740..b543a7ca 100644 --- a/deploy/kustomize/unbound/deployment.yaml +++ b/deploy/kustomize/unbound/deployment.yaml @@ -32,12 +32,14 @@ spec: - secretRef: name: secret-config-env securityContext: - allowPrivilegeEscalation: true - runAsUser: 100 - runAsGroup: 101 capabilities: + drop: + - ALL add: - NET_BIND_SERVICE + - CHOWN + - SETUID + - SETGID ports: - name: dns containerPort: 53 @@ -67,8 +69,5 @@ spec: cpu: 50m memory: 32Mi securityContext: - fsGroup: 101 - fsGroupChangePolicy: OnRootMismatch - runAsNonRoot: true seccompProfile: type: RuntimeDefault diff --git a/target/unbound/Dockerfile b/target/unbound/Dockerfile index 3d171240..653e7f56 100644 --- a/target/unbound/Dockerfile +++ b/target/unbound/Dockerfile @@ -4,11 +4,8 @@ LABEL maintainer="https://github.com/jeboehm/docker-mailserver" LABEL vendor="https://github.com/jeboehm/docker-mailserver" LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true" -RUN chown -R unbound:unbound /etc/unbound && \ - apk add --no-cache bind-tools libcap && \ - setcap 'cap_net_bind_service=+ep' /usr/sbin/unbound -COPY --chown=unbound:unbound rootfs/ / -USER unbound +RUN apk add --no-cache bind-tools +COPY rootfs/ / EXPOSE 53/tcp 53/udp HEALTHCHECK CMD /usr/local/bin/healthcheck.sh