MAS-WP-0003-T03: optional Scaleway TEM relay for replies; unchroot Postfix

Port 25 outbound is blocked on railiance01, so replies need an authenticated
submission relay. Enabled only when Secret mas-relay is mounted; otherwise
non-local mail is refused at RCPT time instead of queueing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 252470@bnt-lap001
Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
This commit is contained in:
tegwick 2026-10-18 11:11:58 +02:00
parent 2a94672239
commit a80f858643
4 changed files with 45 additions and 5 deletions

View file

@ -2,7 +2,7 @@
# Build from the repo root: docker build -f deploy/railiance/Dockerfile .
FROM debian:bookworm-slim
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
postfix python3 python3-jsonschema ssl-cert ca-certificates && rm -rf /var/lib/apt/lists/*
postfix libsasl2-modules python3 python3-jsonschema ssl-cert ca-certificates && rm -rf /var/lib/apt/lists/*
RUN useradd -r -s /usr/sbin/nologin mas
COPY mas_core /app/mas_core
COPY mas_gateway /app/mas_gateway

View file

@ -41,6 +41,28 @@ postconf -e \
"smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem" \
"smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key" \
"inet_protocols=ipv4" "compatibility_level=3.6"
# start-fg does not populate the chroot (no resolv.conf), so DNS fails inside
# it: client rDNS shows "unknown" and a relayhost would not resolve. The
# container already isolates Postfix, so run the daemons unchrooted.
postconf -F '*/*/chroot = n'
# Outbound replies (reply test). Port 25 out is blocked on railiance01, so
# replies go through an authenticated submission relay, and only when its
# credentials are mounted. Only loopback (the ticket service) may relay.
# Without a relay all non-local mail fails fast instead of queueing.
SMTP_ARG=""
if [ -n "$MAS_RELAY" ] && [ -s /etc/mas-relay/username ]; then
printf '%s %s:%s\n' "$MAS_RELAY" "$(cat /etc/mas-relay/username)" \
"$(cat /etc/mas-relay/password)" > /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd && postmap /etc/postfix/sasl_passwd
postconf -e "relayhost=$MAS_RELAY" "smtp_sasl_auth_enable=yes" \
"smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd" \
"smtp_sasl_security_options=noanonymous" "smtp_tls_security_level=secure" \
"smtp_tls_CApath=/etc/ssl/certs"
SMTP_ARG="--smtp 127.0.0.1:25"
else
postconf -e "default_transport=error:5.7.1 outbound mail disabled"
fi
cat >> /etc/postfix/master.cf <<CF
mas unix - n n - - pipe
flags=Rq user=mas argv=/usr/local/bin/mas-gateway
@ -49,9 +71,9 @@ mas unix - n n - - pipe
CF
mkdir -p /var/lib/mas && chown mas /var/lib/mas
# Replies stay in the outbox (no --smtp) until the subdomain has SPF/DKIM.
# Replies are only recorded in the outbox unless a relay is configured above.
su -s /bin/sh mas -c "python3 /app/examples/ticket_service/ticket_service.py \
--db /var/lib/mas/tickets.sqlite3 --listen 127.0.0.1:8025" &
--db /var/lib/mas/tickets.sqlite3 --listen 127.0.0.1:8025 $SMTP_ARG" &
su -s /bin/sh mas -c "python3 /app/capture.py http://127.0.0.1:8025/mas-events \
/var/lib/mas/events.jsonl 127.0.0.1" &
exec postfix start-fg

View file

@ -1,6 +1,7 @@
# MAS live provider test (mail-address-semantics MAS-WP-0003).
# Founder go for A-D: 2026-10-08. Temporary: delete the namespace after the test.
# A namespace + MX pod B LoadBalancer :25 C statute-only ingress D no outbound mail
# D superseded 2026-10-18: operator chose replies via Scaleway TEM (Secret mas-relay).
# IMAGE is replaced with the pushed digest at apply time.
apiVersion: v1
kind: Namespace
@ -34,8 +35,13 @@ spec:
- name: mx
image: IMAGE
args: [mx]
# Outbound replies via Scaleway TEM, enabled only when Secret mas-relay
# (keys username, password) exists. Port 25 out is blocked on the node.
env: [{name: MAS_RELAY, value: "[smtp.tem.scaleway.com]:587"}]
ports: [{name: smtp, containerPort: 25}]
volumeMounts: [{name: evidence, mountPath: /var/lib/mas}]
volumeMounts:
- {name: evidence, mountPath: /var/lib/mas}
- {name: relay, mountPath: /etc/mas-relay, readOnly: true}
readinessProbe: {tcpSocket: {port: 25}, periodSeconds: 10}
resources:
requests: {cpu: 20m, memory: 64Mi}
@ -43,6 +49,8 @@ spec:
volumes:
- name: evidence
persistentVolumeClaim: {claimName: mas-evidence}
- name: relay
secret: {secretName: mas-relay, optional: true}
---
apiVersion: v1
kind: Service

View file

@ -130,7 +130,17 @@ address and what envelope recipient arrives.
Consumer Outlook.com runs on the same Exchange Online infrastructure as
M365, so outbound handling is covered by the two M365 runs. Not covered:
the Outlook-on-the-web compose form (only the native apps were used).
Still open: the reply test (blocked: no outbound mail).
- Reply test: operator chose to set up outbound now (2026-10-18). Port 25
outbound is blocked on railiance01 (to Gmail and Outlook MX it times out;
587 and 465 are open), so replies go through Scaleway Transactional
Email on [smtp.tem.scaleway.com]:587 with SASL. This is enabled only when
Secret `mas-relay` exists; without it, non-local mail fails fast with
5.7.1. Image `mas-live:wp0003-20261018b` also runs Postfix unchrooted:
the chroot lacked resolv.conf, which is why rDNS showed "unknown".
Tested locally against the real TEM endpoint with dummy credentials:
TLS verified, SASL rejected as expected.
Pending: TEM domain verification and DNS at IONOS (operator), Secret,
redeploy, then the reply send.
## Forwarding and list paths