From 7501dbca7dbc349fa767324310d975e9eef50093 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 21:33:42 +1100 Subject: [PATCH 1/8] Add postfix smarthost to the mail namespace --- apps/base/mail/kustomization.yaml | 16 +++ apps/base/mail/smarthost-pdb.yaml | 11 ++ apps/base/mail/smarthost-service.yaml | 39 ++++++++ apps/base/mail/smarthost-statefulset.yaml | 116 ++++++++++++++++++++++ apps/base/mail/smarthost/gateways.cidr | 2 + apps/base/mail/smarthost/main.cf | 42 ++++++++ apps/base/mail/smarthost/master.cf | 38 +++++++ apps/base/mail/smarthost/milter.cidr | 1 + apps/base/mail/smarthost/relay_domains | 1 + apps/base/mail/smarthost/transport | 1 + 10 files changed, 267 insertions(+) create mode 100644 apps/base/mail/smarthost-pdb.yaml create mode 100644 apps/base/mail/smarthost-service.yaml create mode 100644 apps/base/mail/smarthost-statefulset.yaml create mode 100644 apps/base/mail/smarthost/gateways.cidr create mode 100644 apps/base/mail/smarthost/main.cf create mode 100644 apps/base/mail/smarthost/master.cf create mode 100644 apps/base/mail/smarthost/milter.cidr create mode 100644 apps/base/mail/smarthost/relay_domains create mode 100644 apps/base/mail/smarthost/transport diff --git a/apps/base/mail/kustomization.yaml b/apps/base/mail/kustomization.yaml index ebe64ef..6b7f837 100644 --- a/apps/base/mail/kustomization.yaml +++ b/apps/base/mail/kustomization.yaml @@ -6,4 +6,20 @@ resources: - cnpg_backup.yaml - cnpg_cluster.yaml - namespace.yaml + - smarthost-pdb.yaml + - smarthost-service.yaml + - smarthost-statefulset.yaml - stalwart-blobs-bucket.yaml + +configMapGenerator: + - name: smarthost-config + namespace: mail + options: + disableNameSuffixHash: true + files: + - smarthost/main.cf + - smarthost/master.cf + - smarthost/relay_domains + - smarthost/transport + - smarthost/gateways.cidr + - smarthost/milter.cidr diff --git a/apps/base/mail/smarthost-pdb.yaml b/apps/base/mail/smarthost-pdb.yaml new file mode 100644 index 0000000..272d9cf --- /dev/null +++ b/apps/base/mail/smarthost-pdb.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: smarthost + namespace: mail +spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: smarthost diff --git a/apps/base/mail/smarthost-service.yaml b/apps/base/mail/smarthost-service.yaml new file mode 100644 index 0000000..025947a --- /dev/null +++ b/apps/base/mail/smarthost-service.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: smarthost + namespace: mail +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: smarthost + ports: + - name: smtp + port: 25 + targetPort: smtp + - name: submission + port: 587 + targetPort: submission +--- +# Gateways and lab networks; Local keeps client IPs for the postfix CIDR checks. +apiVersion: v1 +kind: Service +metadata: + name: smarthost-lb + namespace: mail + annotations: + purelb.io/addresses: 198.18.200.16 + purelb.io/service-group: common +spec: + type: LoadBalancer + externalTrafficPolicy: Local + selector: + app.kubernetes.io/name: smarthost + ports: + - name: smtp + port: 25 + targetPort: smtp + - name: submission + port: 587 + targetPort: submission diff --git a/apps/base/mail/smarthost-statefulset.yaml b/apps/base/mail/smarthost-statefulset.yaml new file mode 100644 index 0000000..224184e --- /dev/null +++ b/apps/base/mail/smarthost-statefulset.yaml @@ -0,0 +1,116 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: smarthost + namespace: mail + annotations: + configmap.reloader.stakater.com/reload: "smarthost-config" +spec: + serviceName: smarthost + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: smarthost + template: + metadata: + labels: + app.kubernetes.io/name: smarthost + spec: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/name: smarthost + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: postfix + image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/postfix:0.1.0 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 25 + name: smtp + protocol: TCP + - containerPort: 587 + name: submission + protocol: TCP + readinessProbe: + tcpSocket: + port: smtp + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: ["postfix", "status"] + initialDelaySeconds: 30 + periodSeconds: 30 + # postfix master runs as root and drops to the postfix user per service. + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + - SETUID + - SETGID + - NET_BIND_SERVICE + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + volumeMounts: + - name: spool + mountPath: /var/spool/postfix + - name: tls + mountPath: /etc/postfix/tls + readOnly: true + - name: config + mountPath: /etc/postfix/main.cf + subPath: main.cf + readOnly: true + - name: config + mountPath: /etc/postfix/master.cf + subPath: master.cf + readOnly: true + - name: config + mountPath: /etc/postfix/relay_domains + subPath: relay_domains + readOnly: true + - name: config + mountPath: /etc/postfix/transport + subPath: transport + readOnly: true + - name: config + mountPath: /etc/postfix/gateways.cidr + subPath: gateways.cidr + readOnly: true + - name: config + mountPath: /etc/postfix/milter.cidr + subPath: milter.cidr + readOnly: true + volumes: + - name: config + configMap: + name: smarthost-config + - name: tls + secret: + secretName: wildcard-unkin-net-tls + volumeClaimTemplates: + - metadata: + name: spool + spec: + accessModes: [ReadWriteOnce] + storageClassName: cephrbd-fast-delete + resources: + requests: + storage: 5Gi diff --git a/apps/base/mail/smarthost/gateways.cidr b/apps/base/mail/smarthost/gateways.cidr new file mode 100644 index 0000000..de93a9b --- /dev/null +++ b/apps/base/mail/smarthost/gateways.cidr @@ -0,0 +1,2 @@ +# DMZ gateways (mx1/mx2) over WireGuard. +10.10.12.0/24 OK diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf new file mode 100644 index 0000000..9945223 --- /dev/null +++ b/apps/base/mail/smarthost/main.cf @@ -0,0 +1,42 @@ +compatibility_level = 3.5 +myhostname = smarthost.unkin.net +myorigin = unkin.net +mydestination = +alias_maps = +local_recipient_maps = +inet_interfaces = all +inet_protocols = ipv4 +maillog_file = /dev/stdout +message_size_limit = 52428800 + +# Pod CIDR (Stalwart) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. +pod_networks = 10.42.0.0/16 +mynetworks = $pod_networks + 10.10.12.200/32 198.18.1.10/32 + 198.18.2.160/27 198.18.2.192/27 198.18.21.160/27 198.18.21.192/27 + 198.18.13.0/24 198.18.14.0/24 198.18.15.0/24 198.18.16.0/24 198.18.17.0/24 198.18.18.0/24 + 198.18.19.0/24 198.18.20.0/24 198.18.21.0/24 198.18.22.0/24 198.18.23.0/24 + 198.18.25.0/24 198.18.26.0/24 198.18.27.0/24 198.18.28.0/24 198.18.29.0/24 + 198.18.64.0/24 198.18.65.0/24 198.18.66.0/24 198.18.67.0/24 198.18.68.0/24 198.18.69.0/24 + +relay_domains = texthash:/etc/postfix/relay_domains +transport_maps = texthash:/etc/postfix/transport +relayhost = [mx1.wg.unkin.net]:25 +smtp_fallback_relay = [mx2.wg.unkin.net]:25 +smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination +unverified_recipient_reject_code = 550 + +milter_default_action = tempfail +rspamd_milter = inet:rspamd.mail.svc:11332 + +# Referenced from master.cf per listener. +inbound_client_restrictions = permit_mynetworks, check_client_access cidr:/etc/postfix/gateways.cidr, reject +inbound_recipient_restrictions = permit_mynetworks, reject_unverified_recipient +submission_client_restrictions = permit_mynetworks, reject + +smtpd_tls_security_level = may +smtpd_tls_cert_file = /etc/postfix/tls/tls.crt +smtpd_tls_key_file = /etc/postfix/tls/tls.key +smtpd_tls_received_header = yes +smtp_tls_security_level = may +lmtp_tls_security_level = none diff --git a/apps/base/mail/smarthost/master.cf b/apps/base/mail/smarthost/master.cf new file mode 100644 index 0000000..ed18250 --- /dev/null +++ b/apps/base/mail/smarthost/master.cf @@ -0,0 +1,38 @@ +# inbound-relay: gateways (no milter) and Stalwart's outbound relay (pod CIDR, rspamd milter). +25 inet n - n - - smtpd + -o syslog_name=postfix/inbound-relay + -o mynetworks=$pod_networks + -o smtpd_client_restrictions=$inbound_client_restrictions + -o smtpd_recipient_restrictions=$inbound_recipient_restrictions + -o smtpd_milters= + -o smtpd_milter_maps=cidr:/etc/postfix/milter.cidr +# submission-internal: lab networks and pods, always through rspamd. +587 inet n - n - - smtpd + -o syslog_name=postfix/submission-internal + -o smtpd_client_restrictions=$submission_client_restrictions + -o smtpd_milters=$rspamd_milter +pickup unix n - n 60 1 pickup +cleanup unix n - n - 0 cleanup +qmgr unix n - n 300 1 qmgr +tlsmgr unix - - n 1000? 1 tlsmgr +rewrite unix - - n - - trivial-rewrite +bounce unix - - n - 0 bounce +defer unix - - n - 0 bounce +trace unix - - n - 0 bounce +verify unix - - n - 1 verify +flush unix n - n 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - n - - smtp +relay unix - - n - - smtp + -o syslog_name=postfix/$service_name +showq unix n - n - - showq +error unix - - n - - error +retry unix - - n - - error +discard unix - - n - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - n - - lmtp +anvil unix - - n - 1 anvil +scache unix - - n - 1 scache +postlog unix-dgram n - n - 1 postlogd diff --git a/apps/base/mail/smarthost/milter.cidr b/apps/base/mail/smarthost/milter.cidr new file mode 100644 index 0000000..3f75361 --- /dev/null +++ b/apps/base/mail/smarthost/milter.cidr @@ -0,0 +1 @@ +10.42.0.0/16 inet:rspamd.mail.svc:11332 diff --git a/apps/base/mail/smarthost/relay_domains b/apps/base/mail/smarthost/relay_domains new file mode 100644 index 0000000..59818b2 --- /dev/null +++ b/apps/base/mail/smarthost/relay_domains @@ -0,0 +1 @@ +unkin.net OK diff --git a/apps/base/mail/smarthost/transport b/apps/base/mail/smarthost/transport new file mode 100644 index 0000000..828306f --- /dev/null +++ b/apps/base/mail/smarthost/transport @@ -0,0 +1 @@ +unkin.net lmtp:[stalwart.mail.svc.cluster.local]:24 -- 2.47.3 From 92f6c1472bb7922f7c55c0911cf63ee10237ca16 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 21:42:28 +1100 Subject: [PATCH 2/8] Restrict smarthost ingress and shorten negative recipient cache --- apps/base/mail/kustomization.yaml | 1 + apps/base/mail/smarthost-networkpolicy.yaml | 76 +++++++++++++++++++++ apps/base/mail/smarthost/main.cf | 5 +- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 apps/base/mail/smarthost-networkpolicy.yaml diff --git a/apps/base/mail/kustomization.yaml b/apps/base/mail/kustomization.yaml index 6b7f837..4c16052 100644 --- a/apps/base/mail/kustomization.yaml +++ b/apps/base/mail/kustomization.yaml @@ -6,6 +6,7 @@ resources: - cnpg_backup.yaml - cnpg_cluster.yaml - namespace.yaml + - smarthost-networkpolicy.yaml - smarthost-pdb.yaml - smarthost-service.yaml - smarthost-statefulset.yaml diff --git a/apps/base/mail/smarthost-networkpolicy.yaml b/apps/base/mail/smarthost-networkpolicy.yaml new file mode 100644 index 0000000..70cec03 --- /dev/null +++ b/apps/base/mail/smarthost-networkpolicy.yaml @@ -0,0 +1,76 @@ +--- +# Gateways, lab networks (incl. node IPs for kubelet probes) and opted-in pods in any namespace. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: smarthost + namespace: mail +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: smarthost + policyTypes: + - Ingress + ingress: + - from: + - ipBlock: + cidr: 10.10.12.0/24 + - ipBlock: + cidr: 198.18.1.10/32 + - ipBlock: + cidr: 198.18.2.160/27 + - ipBlock: + cidr: 198.18.2.192/27 + - ipBlock: + cidr: 198.18.13.0/24 + - ipBlock: + cidr: 198.18.14.0/24 + - ipBlock: + cidr: 198.18.15.0/24 + - ipBlock: + cidr: 198.18.16.0/24 + - ipBlock: + cidr: 198.18.17.0/24 + - ipBlock: + cidr: 198.18.18.0/24 + - ipBlock: + cidr: 198.18.19.0/24 + - ipBlock: + cidr: 198.18.20.0/24 + - ipBlock: + cidr: 198.18.21.0/24 + - ipBlock: + cidr: 198.18.22.0/24 + - ipBlock: + cidr: 198.18.23.0/24 + - ipBlock: + cidr: 198.18.25.0/24 + - ipBlock: + cidr: 198.18.26.0/24 + - ipBlock: + cidr: 198.18.27.0/24 + - ipBlock: + cidr: 198.18.28.0/24 + - ipBlock: + cidr: 198.18.29.0/24 + - ipBlock: + cidr: 198.18.64.0/24 + - ipBlock: + cidr: 198.18.65.0/24 + - ipBlock: + cidr: 198.18.66.0/24 + - ipBlock: + cidr: 198.18.67.0/24 + - ipBlock: + cidr: 198.18.68.0/24 + - ipBlock: + cidr: 198.18.69.0/24 + - namespaceSelector: {} + podSelector: + matchLabels: + mail.unkin.net/smarthost-client: "true" + ports: + - port: smtp + protocol: TCP + - port: submission + protocol: TCP diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index 9945223..a66aa35 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -9,7 +9,7 @@ inet_protocols = ipv4 maillog_file = /dev/stdout message_size_limit = 52428800 -# Pod CIDR (Stalwart) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. +# Pod CIDR (Stalwart; narrowed by smarthost-networkpolicy.yaml) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. pod_networks = 10.42.0.0/16 mynetworks = $pod_networks 10.10.12.200/32 198.18.1.10/32 @@ -25,6 +25,9 @@ relayhost = [mx1.wg.unkin.net]:25 smtp_fallback_relay = [mx2.wg.unkin.net]:25 smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination unverified_recipient_reject_code = 550 +unverified_recipient_tempfail_action = defer_if_permit +address_verify_negative_refresh_time = 5m +address_verify_negative_expire_time = 1h milter_default_action = tempfail rspamd_milter = inet:rspamd.mail.svc:11332 -- 2.47.3 From 8105ba11f4e45b0aa0fd34659fafc45e94db4b01 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 21:46:32 +1100 Subject: [PATCH 3/8] Serve all smarthost clients on port 25, rename gateways to mta1/mta2, grow spool to 50Gi --- apps/base/mail/smarthost-networkpolicy.yaml | 2 -- apps/base/mail/smarthost-service.yaml | 6 ------ apps/base/mail/smarthost-statefulset.yaml | 5 +---- apps/base/mail/smarthost/gateways.cidr | 2 +- apps/base/mail/smarthost/main.cf | 18 +++++++++--------- apps/base/mail/smarthost/master.cf | 13 +------------ apps/base/mail/smarthost/milter.cidr | 5 ++++- 7 files changed, 16 insertions(+), 35 deletions(-) diff --git a/apps/base/mail/smarthost-networkpolicy.yaml b/apps/base/mail/smarthost-networkpolicy.yaml index 70cec03..3e055cb 100644 --- a/apps/base/mail/smarthost-networkpolicy.yaml +++ b/apps/base/mail/smarthost-networkpolicy.yaml @@ -72,5 +72,3 @@ spec: ports: - port: smtp protocol: TCP - - port: submission - protocol: TCP diff --git a/apps/base/mail/smarthost-service.yaml b/apps/base/mail/smarthost-service.yaml index 025947a..aabcec7 100644 --- a/apps/base/mail/smarthost-service.yaml +++ b/apps/base/mail/smarthost-service.yaml @@ -12,9 +12,6 @@ spec: - name: smtp port: 25 targetPort: smtp - - name: submission - port: 587 - targetPort: submission --- # Gateways and lab networks; Local keeps client IPs for the postfix CIDR checks. apiVersion: v1 @@ -34,6 +31,3 @@ spec: - name: smtp port: 25 targetPort: smtp - - name: submission - port: 587 - targetPort: submission diff --git a/apps/base/mail/smarthost-statefulset.yaml b/apps/base/mail/smarthost-statefulset.yaml index 224184e..da5418a 100644 --- a/apps/base/mail/smarthost-statefulset.yaml +++ b/apps/base/mail/smarthost-statefulset.yaml @@ -35,9 +35,6 @@ spec: - containerPort: 25 name: smtp protocol: TCP - - containerPort: 587 - name: submission - protocol: TCP readinessProbe: tcpSocket: port: smtp @@ -113,4 +110,4 @@ spec: storageClassName: cephrbd-fast-delete resources: requests: - storage: 5Gi + storage: 50Gi diff --git a/apps/base/mail/smarthost/gateways.cidr b/apps/base/mail/smarthost/gateways.cidr index de93a9b..9e220d4 100644 --- a/apps/base/mail/smarthost/gateways.cidr +++ b/apps/base/mail/smarthost/gateways.cidr @@ -1,2 +1,2 @@ -# DMZ gateways (mx1/mx2) over WireGuard. +# DMZ gateways (mta1/mta2) over WireGuard. 10.10.12.0/24 OK diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index a66aa35..0dd1e83 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -9,7 +9,8 @@ inet_protocols = ipv4 maillog_file = /dev/stdout message_size_limit = 52428800 -# Pod CIDR (Stalwart; narrowed by smarthost-networkpolicy.yaml) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. +# Relay-anywhere clients: pod CIDR (narrowed by smarthost-networkpolicy.yaml) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. +# Keep in sync with the NetworkPolicy; gateways must stay out of here. pod_networks = 10.42.0.0/16 mynetworks = $pod_networks 10.10.12.200/32 198.18.1.10/32 @@ -21,21 +22,20 @@ mynetworks = $pod_networks relay_domains = texthash:/etc/postfix/relay_domains transport_maps = texthash:/etc/postfix/transport -relayhost = [mx1.wg.unkin.net]:25 -smtp_fallback_relay = [mx2.wg.unkin.net]:25 +relayhost = [mta1.wg.unkin.net]:25 +smtp_fallback_relay = [mta2.wg.unkin.net]:25 +smtpd_client_restrictions = permit_mynetworks, check_client_access cidr:/etc/postfix/gateways.cidr, reject smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination +smtpd_recipient_restrictions = permit_mynetworks, reject_unverified_recipient unverified_recipient_reject_code = 550 unverified_recipient_tempfail_action = defer_if_permit address_verify_negative_refresh_time = 5m address_verify_negative_expire_time = 1h +# Gateways skip rspamd via milter.cidr; everyone else admitted goes through it. milter_default_action = tempfail -rspamd_milter = inet:rspamd.mail.svc:11332 - -# Referenced from master.cf per listener. -inbound_client_restrictions = permit_mynetworks, check_client_access cidr:/etc/postfix/gateways.cidr, reject -inbound_recipient_restrictions = permit_mynetworks, reject_unverified_recipient -submission_client_restrictions = permit_mynetworks, reject +smtpd_milters = +smtpd_milter_maps = cidr:/etc/postfix/milter.cidr smtpd_tls_security_level = may smtpd_tls_cert_file = /etc/postfix/tls/tls.crt diff --git a/apps/base/mail/smarthost/master.cf b/apps/base/mail/smarthost/master.cf index ed18250..2f3bf5f 100644 --- a/apps/base/mail/smarthost/master.cf +++ b/apps/base/mail/smarthost/master.cf @@ -1,16 +1,5 @@ -# inbound-relay: gateways (no milter) and Stalwart's outbound relay (pod CIDR, rspamd milter). +# Gateways (inbound to relay_domains, no milter) and mynetworks (relay anywhere via rspamd). 25 inet n - n - - smtpd - -o syslog_name=postfix/inbound-relay - -o mynetworks=$pod_networks - -o smtpd_client_restrictions=$inbound_client_restrictions - -o smtpd_recipient_restrictions=$inbound_recipient_restrictions - -o smtpd_milters= - -o smtpd_milter_maps=cidr:/etc/postfix/milter.cidr -# submission-internal: lab networks and pods, always through rspamd. -587 inet n - n - - smtpd - -o syslog_name=postfix/submission-internal - -o smtpd_client_restrictions=$submission_client_restrictions - -o smtpd_milters=$rspamd_milter pickup unix n - n 60 1 pickup cleanup unix n - n - 0 cleanup qmgr unix n - n 300 1 qmgr diff --git a/apps/base/mail/smarthost/milter.cidr b/apps/base/mail/smarthost/milter.cidr index 3f75361..e66a085 100644 --- a/apps/base/mail/smarthost/milter.cidr +++ b/apps/base/mail/smarthost/milter.cidr @@ -1 +1,4 @@ -10.42.0.0/16 inet:rspamd.mail.svc:11332 +# First match wins: gateways skip rspamd, every other admitted client goes through it. +10.10.12.200/32 inet:rspamd.mail.svc:11332 +10.10.12.0/24 DISABLE +0.0.0.0/0 inet:rspamd.mail.svc:11332 -- 2.47.3 From a27023c7c946993dbb95cb956f3c718147d35a02 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 21:48:08 +1100 Subject: [PATCH 4/8] Relay outbound via mta1/mta2.edge.unkin.net --- apps/base/mail/smarthost/main.cf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index 0dd1e83..fdcbc80 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -22,8 +22,8 @@ mynetworks = $pod_networks relay_domains = texthash:/etc/postfix/relay_domains transport_maps = texthash:/etc/postfix/transport -relayhost = [mta1.wg.unkin.net]:25 -smtp_fallback_relay = [mta2.wg.unkin.net]:25 +relayhost = [mta1.edge.unkin.net]:25 +smtp_fallback_relay = [mta2.edge.unkin.net]:25 smtpd_client_restrictions = permit_mynetworks, check_client_access cidr:/etc/postfix/gateways.cidr, reject smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination smtpd_recipient_restrictions = permit_mynetworks, reject_unverified_recipient -- 2.47.3 From 4f6150e93ae1e4974d2622906bdf9e6ee3ae1f97 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 21:52:54 +1100 Subject: [PATCH 5/8] Require TLS between smarthost and gateways over public IPs, merge mynetworks and milter map --- apps/base/mail/kustomization.yaml | 3 +- apps/base/mail/smarthost-networkpolicy.yaml | 8 ++++-- apps/base/mail/smarthost-statefulset.yaml | 8 ++++-- apps/base/mail/smarthost/gateways.cidr | 5 ++-- apps/base/mail/smarthost/main.cf | 17 ++++------- apps/base/mail/smarthost/milter.cidr | 4 --- apps/base/mail/smarthost/mynetworks.cidr | 31 +++++++++++++++++++++ apps/base/mail/smarthost/tls_policy | 2 ++ 8 files changed, 55 insertions(+), 23 deletions(-) delete mode 100644 apps/base/mail/smarthost/milter.cidr create mode 100644 apps/base/mail/smarthost/mynetworks.cidr create mode 100644 apps/base/mail/smarthost/tls_policy diff --git a/apps/base/mail/kustomization.yaml b/apps/base/mail/kustomization.yaml index 4c16052..603ae53 100644 --- a/apps/base/mail/kustomization.yaml +++ b/apps/base/mail/kustomization.yaml @@ -23,4 +23,5 @@ configMapGenerator: - smarthost/relay_domains - smarthost/transport - smarthost/gateways.cidr - - smarthost/milter.cidr + - smarthost/mynetworks.cidr + - smarthost/tls_policy diff --git a/apps/base/mail/smarthost-networkpolicy.yaml b/apps/base/mail/smarthost-networkpolicy.yaml index 3e055cb..05f4c67 100644 --- a/apps/base/mail/smarthost-networkpolicy.yaml +++ b/apps/base/mail/smarthost-networkpolicy.yaml @@ -1,5 +1,5 @@ --- -# Gateways, lab networks (incl. node IPs for kubelet probes) and opted-in pods in any namespace. +# Gateways (public IPs, see smarthost/gateways.cidr), lab networks (incl. node IPs for kubelet probes) and opted-in pods in any namespace. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -14,7 +14,11 @@ spec: ingress: - from: - ipBlock: - cidr: 10.10.12.0/24 + cidr: 192.0.2.1/32 + - ipBlock: + cidr: 192.0.2.2/32 + - ipBlock: + cidr: 10.10.12.200/32 - ipBlock: cidr: 198.18.1.10/32 - ipBlock: diff --git a/apps/base/mail/smarthost-statefulset.yaml b/apps/base/mail/smarthost-statefulset.yaml index da5418a..9a401c5 100644 --- a/apps/base/mail/smarthost-statefulset.yaml +++ b/apps/base/mail/smarthost-statefulset.yaml @@ -92,8 +92,12 @@ spec: subPath: gateways.cidr readOnly: true - name: config - mountPath: /etc/postfix/milter.cidr - subPath: milter.cidr + mountPath: /etc/postfix/mynetworks.cidr + subPath: mynetworks.cidr + readOnly: true + - name: config + mountPath: /etc/postfix/tls_policy + subPath: tls_policy readOnly: true volumes: - name: config diff --git a/apps/base/mail/smarthost/gateways.cidr b/apps/base/mail/smarthost/gateways.cidr index 9e220d4..c3d1c3b 100644 --- a/apps/base/mail/smarthost/gateways.cidr +++ b/apps/base/mail/smarthost/gateways.cidr @@ -1,2 +1,3 @@ -# DMZ gateways (mta1/mta2) over WireGuard. -10.10.12.0/24 OK +# Gateway (mta1/mta2) public IPs; TEST-NET placeholders until the BinaryLane servers exist. +192.0.2.1/32 reject_plaintext_session, permit +192.0.2.2/32 reject_plaintext_session, permit diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index fdcbc80..dde1f78 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -9,21 +9,15 @@ inet_protocols = ipv4 maillog_file = /dev/stdout message_size_limit = 52428800 -# Relay-anywhere clients: pod CIDR (narrowed by smarthost-networkpolicy.yaml) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. -# Keep in sync with the NetworkPolicy; gateways must stay out of here. -pod_networks = 10.42.0.0/16 -mynetworks = $pod_networks - 10.10.12.200/32 198.18.1.10/32 - 198.18.2.160/27 198.18.2.192/27 198.18.21.160/27 198.18.21.192/27 - 198.18.13.0/24 198.18.14.0/24 198.18.15.0/24 198.18.16.0/24 198.18.17.0/24 198.18.18.0/24 - 198.18.19.0/24 198.18.20.0/24 198.18.21.0/24 198.18.22.0/24 198.18.23.0/24 - 198.18.25.0/24 198.18.26.0/24 198.18.27.0/24 198.18.28.0/24 198.18.29.0/24 - 198.18.64.0/24 198.18.65.0/24 198.18.66.0/24 198.18.67.0/24 198.18.68.0/24 198.18.69.0/24 +# Relay-anywhere clients, all routed through rspamd for DKIM signing; gateways must stay out. +mynetworks = cidr:/etc/postfix/mynetworks.cidr relay_domains = texthash:/etc/postfix/relay_domains transport_maps = texthash:/etc/postfix/transport relayhost = [mta1.edge.unkin.net]:25 smtp_fallback_relay = [mta2.edge.unkin.net]:25 +smtp_tls_policy_maps = texthash:/etc/postfix/tls_policy +smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt smtpd_client_restrictions = permit_mynetworks, check_client_access cidr:/etc/postfix/gateways.cidr, reject smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination smtpd_recipient_restrictions = permit_mynetworks, reject_unverified_recipient @@ -32,10 +26,9 @@ unverified_recipient_tempfail_action = defer_if_permit address_verify_negative_refresh_time = 5m address_verify_negative_expire_time = 1h -# Gateways skip rspamd via milter.cidr; everyone else admitted goes through it. milter_default_action = tempfail smtpd_milters = -smtpd_milter_maps = cidr:/etc/postfix/milter.cidr +smtpd_milter_maps = cidr:/etc/postfix/mynetworks.cidr smtpd_tls_security_level = may smtpd_tls_cert_file = /etc/postfix/tls/tls.crt diff --git a/apps/base/mail/smarthost/milter.cidr b/apps/base/mail/smarthost/milter.cidr deleted file mode 100644 index e66a085..0000000 --- a/apps/base/mail/smarthost/milter.cidr +++ /dev/null @@ -1,4 +0,0 @@ -# First match wins: gateways skip rspamd, every other admitted client goes through it. -10.10.12.200/32 inet:rspamd.mail.svc:11332 -10.10.12.0/24 DISABLE -0.0.0.0/0 inet:rspamd.mail.svc:11332 diff --git a/apps/base/mail/smarthost/mynetworks.cidr b/apps/base/mail/smarthost/mynetworks.cidr new file mode 100644 index 0000000..32779ce --- /dev/null +++ b/apps/base/mail/smarthost/mynetworks.cidr @@ -0,0 +1,31 @@ +# Pod CIDR (narrowed by smarthost-networkpolicy.yaml) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. +# Doubles as mynetworks and the milter map; keep in sync with the NetworkPolicy. +10.42.0.0/16 inet:rspamd.mail.svc:11332 +10.10.12.200/32 inet:rspamd.mail.svc:11332 +198.18.1.10/32 inet:rspamd.mail.svc:11332 +198.18.2.160/27 inet:rspamd.mail.svc:11332 +198.18.2.192/27 inet:rspamd.mail.svc:11332 +198.18.21.160/27 inet:rspamd.mail.svc:11332 +198.18.21.192/27 inet:rspamd.mail.svc:11332 +198.18.13.0/24 inet:rspamd.mail.svc:11332 +198.18.14.0/24 inet:rspamd.mail.svc:11332 +198.18.15.0/24 inet:rspamd.mail.svc:11332 +198.18.16.0/24 inet:rspamd.mail.svc:11332 +198.18.17.0/24 inet:rspamd.mail.svc:11332 +198.18.18.0/24 inet:rspamd.mail.svc:11332 +198.18.19.0/24 inet:rspamd.mail.svc:11332 +198.18.20.0/24 inet:rspamd.mail.svc:11332 +198.18.21.0/24 inet:rspamd.mail.svc:11332 +198.18.22.0/24 inet:rspamd.mail.svc:11332 +198.18.23.0/24 inet:rspamd.mail.svc:11332 +198.18.25.0/24 inet:rspamd.mail.svc:11332 +198.18.26.0/24 inet:rspamd.mail.svc:11332 +198.18.27.0/24 inet:rspamd.mail.svc:11332 +198.18.28.0/24 inet:rspamd.mail.svc:11332 +198.18.29.0/24 inet:rspamd.mail.svc:11332 +198.18.64.0/24 inet:rspamd.mail.svc:11332 +198.18.65.0/24 inet:rspamd.mail.svc:11332 +198.18.66.0/24 inet:rspamd.mail.svc:11332 +198.18.67.0/24 inet:rspamd.mail.svc:11332 +198.18.68.0/24 inet:rspamd.mail.svc:11332 +198.18.69.0/24 inet:rspamd.mail.svc:11332 diff --git a/apps/base/mail/smarthost/tls_policy b/apps/base/mail/smarthost/tls_policy new file mode 100644 index 0000000..dd1481a --- /dev/null +++ b/apps/base/mail/smarthost/tls_policy @@ -0,0 +1,2 @@ +[mta1.edge.unkin.net]:25 secure match=hostname +[mta2.edge.unkin.net]:25 secure match=hostname -- 2.47.3 From d19c7d7942c87202f3d80461d5c93993cebb299c Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 22:01:08 +1100 Subject: [PATCH 6/8] Stop relay_domains matching subdomains, refresh positive verify cache hourly --- apps/base/mail/smarthost/main.cf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index dde1f78..865db83 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -13,6 +13,8 @@ message_size_limit = 52428800 mynetworks = cidr:/etc/postfix/mynetworks.cidr relay_domains = texthash:/etc/postfix/relay_domains +# Default minus relay_domains: subdomains are not delivered and would loop back out via the gateways. +parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,permit_mx_backup_networks,qmqpd_authorized_clients,smtpd_access_maps transport_maps = texthash:/etc/postfix/transport relayhost = [mta1.edge.unkin.net]:25 smtp_fallback_relay = [mta2.edge.unkin.net]:25 @@ -25,6 +27,8 @@ unverified_recipient_reject_code = 550 unverified_recipient_tempfail_action = defer_if_permit address_verify_negative_refresh_time = 5m address_verify_negative_expire_time = 1h +address_verify_positive_refresh_time = 1h +address_verify_positive_expire_time = 1d milter_default_action = tempfail smtpd_milters = -- 2.47.3 From 926967b2981f32343501eeec75c5dbc297cb71b0 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 22:05:50 +1100 Subject: [PATCH 7/8] Route gateway mail through the rspamd milter, accept XFORWARD from gateways --- apps/base/mail/smarthost/main.cf | 6 +-- apps/base/mail/smarthost/master.cf | 2 +- apps/base/mail/smarthost/mynetworks.cidr | 60 ++++++++++++------------ 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index 865db83..7c4ca46 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -9,7 +9,7 @@ inet_protocols = ipv4 maillog_file = /dev/stdout message_size_limit = 52428800 -# Relay-anywhere clients, all routed through rspamd for DKIM signing; gateways must stay out. +# Relay-anywhere clients; gateways must stay out. mynetworks = cidr:/etc/postfix/mynetworks.cidr relay_domains = texthash:/etc/postfix/relay_domains @@ -31,8 +31,8 @@ address_verify_positive_refresh_time = 1h address_verify_positive_expire_time = 1d milter_default_action = tempfail -smtpd_milters = -smtpd_milter_maps = cidr:/etc/postfix/mynetworks.cidr +smtpd_milters = inet:rspamd.mail.svc:11332 +smtpd_authorized_xforward_hosts = cidr:/etc/postfix/gateways.cidr smtpd_tls_security_level = may smtpd_tls_cert_file = /etc/postfix/tls/tls.crt diff --git a/apps/base/mail/smarthost/master.cf b/apps/base/mail/smarthost/master.cf index 2f3bf5f..94ed1f7 100644 --- a/apps/base/mail/smarthost/master.cf +++ b/apps/base/mail/smarthost/master.cf @@ -1,4 +1,4 @@ -# Gateways (inbound to relay_domains, no milter) and mynetworks (relay anywhere via rspamd). +# Gateways (inbound to relay_domains) and mynetworks (relay anywhere); all via the rspamd milter. 25 inet n - n - - smtpd pickup unix n - n 60 1 pickup cleanup unix n - n - 0 cleanup diff --git a/apps/base/mail/smarthost/mynetworks.cidr b/apps/base/mail/smarthost/mynetworks.cidr index 32779ce..c9f5e9d 100644 --- a/apps/base/mail/smarthost/mynetworks.cidr +++ b/apps/base/mail/smarthost/mynetworks.cidr @@ -1,31 +1,31 @@ # Pod CIDR (narrowed by smarthost-networkpolicy.yaml) plus the lab networks from bind-internal acl-main/acl-common/acl-nomad-jobs. -# Doubles as mynetworks and the milter map; keep in sync with the NetworkPolicy. -10.42.0.0/16 inet:rspamd.mail.svc:11332 -10.10.12.200/32 inet:rspamd.mail.svc:11332 -198.18.1.10/32 inet:rspamd.mail.svc:11332 -198.18.2.160/27 inet:rspamd.mail.svc:11332 -198.18.2.192/27 inet:rspamd.mail.svc:11332 -198.18.21.160/27 inet:rspamd.mail.svc:11332 -198.18.21.192/27 inet:rspamd.mail.svc:11332 -198.18.13.0/24 inet:rspamd.mail.svc:11332 -198.18.14.0/24 inet:rspamd.mail.svc:11332 -198.18.15.0/24 inet:rspamd.mail.svc:11332 -198.18.16.0/24 inet:rspamd.mail.svc:11332 -198.18.17.0/24 inet:rspamd.mail.svc:11332 -198.18.18.0/24 inet:rspamd.mail.svc:11332 -198.18.19.0/24 inet:rspamd.mail.svc:11332 -198.18.20.0/24 inet:rspamd.mail.svc:11332 -198.18.21.0/24 inet:rspamd.mail.svc:11332 -198.18.22.0/24 inet:rspamd.mail.svc:11332 -198.18.23.0/24 inet:rspamd.mail.svc:11332 -198.18.25.0/24 inet:rspamd.mail.svc:11332 -198.18.26.0/24 inet:rspamd.mail.svc:11332 -198.18.27.0/24 inet:rspamd.mail.svc:11332 -198.18.28.0/24 inet:rspamd.mail.svc:11332 -198.18.29.0/24 inet:rspamd.mail.svc:11332 -198.18.64.0/24 inet:rspamd.mail.svc:11332 -198.18.65.0/24 inet:rspamd.mail.svc:11332 -198.18.66.0/24 inet:rspamd.mail.svc:11332 -198.18.67.0/24 inet:rspamd.mail.svc:11332 -198.18.68.0/24 inet:rspamd.mail.svc:11332 -198.18.69.0/24 inet:rspamd.mail.svc:11332 +# Keep in sync with the NetworkPolicy. +10.42.0.0/16 OK +10.10.12.200/32 OK +198.18.1.10/32 OK +198.18.2.160/27 OK +198.18.2.192/27 OK +198.18.21.160/27 OK +198.18.21.192/27 OK +198.18.13.0/24 OK +198.18.14.0/24 OK +198.18.15.0/24 OK +198.18.16.0/24 OK +198.18.17.0/24 OK +198.18.18.0/24 OK +198.18.19.0/24 OK +198.18.20.0/24 OK +198.18.21.0/24 OK +198.18.22.0/24 OK +198.18.23.0/24 OK +198.18.25.0/24 OK +198.18.26.0/24 OK +198.18.27.0/24 OK +198.18.28.0/24 OK +198.18.29.0/24 OK +198.18.64.0/24 OK +198.18.65.0/24 OK +198.18.66.0/24 OK +198.18.67.0/24 OK +198.18.68.0/24 OK +198.18.69.0/24 OK -- 2.47.3 From 3019291e5cb83e208ff32d086728c2444b8a3bf9 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Mon, 12 Oct 2026 22:15:11 +1100 Subject: [PATCH 8/8] Name the smarthost mail-in.main.unkin.net and serve the *.main.unkin.net wildcard --- .../cert-manager/certificate_wildcard-main-unkin-net.yaml | 6 +++--- apps/base/mail/smarthost-statefulset.yaml | 2 +- apps/base/mail/smarthost/main.cf | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/base/cert-manager/certificate_wildcard-main-unkin-net.yaml b/apps/base/cert-manager/certificate_wildcard-main-unkin-net.yaml index bc34a2b..8bed1a2 100644 --- a/apps/base/cert-manager/certificate_wildcard-main-unkin-net.yaml +++ b/apps/base/cert-manager/certificate_wildcard-main-unkin-net.yaml @@ -1,6 +1,6 @@ --- # Let's Encrypt *.main.unkin.net wildcard for the haproxy edge (pve, arr stack, -# jellyfin, stalwart webadmin/autoconfig). DNS-01 needs the delegated +# jellyfin, stalwart webadmin/autoconfig) and the mail smarthost. DNS-01 needs the delegated # _acme-challenge.main.unkin.net CNAME in the public unkin.net zone. # _acme-challenge.main.unkin.net. CNAME _acme-challenge.main.acme.unkin.net. apiVersion: cert-manager.io/v1 @@ -13,9 +13,9 @@ spec: secretTemplate: annotations: reflector.v1.k8s.emberstack.com/reflection-allowed: "true" - reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "haproxy" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "haproxy,mail" reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" - reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "haproxy" + reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "haproxy,mail" privateKey: size: 4096 dnsNames: diff --git a/apps/base/mail/smarthost-statefulset.yaml b/apps/base/mail/smarthost-statefulset.yaml index 9a401c5..2bd4527 100644 --- a/apps/base/mail/smarthost-statefulset.yaml +++ b/apps/base/mail/smarthost-statefulset.yaml @@ -105,7 +105,7 @@ spec: name: smarthost-config - name: tls secret: - secretName: wildcard-unkin-net-tls + secretName: wildcard-main-unkin-net-tls volumeClaimTemplates: - metadata: name: spool diff --git a/apps/base/mail/smarthost/main.cf b/apps/base/mail/smarthost/main.cf index 7c4ca46..5f85dd2 100644 --- a/apps/base/mail/smarthost/main.cf +++ b/apps/base/mail/smarthost/main.cf @@ -1,5 +1,5 @@ compatibility_level = 3.5 -myhostname = smarthost.unkin.net +myhostname = mail-in.main.unkin.net myorigin = unkin.net mydestination = alias_maps = -- 2.47.3