MAS-WP-0004: Postfix integration profile, corpus probe in pytest, Exim notes
Profile in integrations/postfix (install.sh + README), now used by the live deployment. Probe runs every conformance-corpus local-part and the relay negatives against it; tests/test_postfix_profile.py runs it where Docker is available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5787@bnt-lap001 Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
This commit is contained in:
parent
082d4c39eb
commit
d766d61e7f
12 changed files with 1029 additions and 98 deletions
|
|
@ -8,6 +8,7 @@ COPY mas_core /app/mas_core
|
|||
COPY mas_gateway /app/mas_gateway
|
||||
COPY examples /app/examples
|
||||
COPY experiments/postfix-probe/capture.py /app/capture.py
|
||||
COPY integrations /app/integrations
|
||||
COPY deploy/railiance/entrypoint.sh /entrypoint.sh
|
||||
ENV PYTHONPATH=/app
|
||||
EXPOSE 25 8080
|
||||
|
|
|
|||
|
|
@ -15,28 +15,22 @@ exec env PYTHONPATH=/app python3 -m mas_gateway.main "\$@"
|
|||
SH
|
||||
chmod +x /usr/local/bin/mas-gateway
|
||||
|
||||
# Not an open relay: no relay_domains, mynetworks is loopback only, so the
|
||||
# only accepted recipients are @$DOMAIN, all delivered to the mas pipe.
|
||||
# Stock reject_unauth_destination refuses any local-part containing '!' or '%'
|
||||
# (treated as sender-specified routing), which kills every P2 address, and an
|
||||
# access-map result is ignored for such routed addresses unless
|
||||
# allow_untrusted_routing=yes (safe here: relay_domains is empty). So relay control is an
|
||||
# explicit regexp whose match runs the `permit` restriction (not OK) for
|
||||
# anything ending in @$DOMAIN; everything else falls through to `reject`
|
||||
# (Postfix refuses to start smtpd without a reject-class entry in the list).
|
||||
# With swap_bangpath/percent_hack off and local_transport=mas, those
|
||||
# addresses can only reach the mas pipe.
|
||||
DOMAIN_RE=$(printf '%s' "$DOMAIN" | sed 's/\./\\./g')
|
||||
printf '/@%s$/ permit\n' "$DOMAIN_RE" > /etc/postfix/mas_rcpt.regexp
|
||||
# The MAS setup (relay control, no rewriting, mas pipe, outbound relay, sender
|
||||
# authentication) is the integration profile; see integrations/postfix/.
|
||||
# Port 25 out is blocked on railiance01, so replies go through an
|
||||
# authenticated submission relay, and only when its credentials are mounted.
|
||||
RELAY_ARGS=""
|
||||
if [ -n "$MAS_RELAY" ] && [ -s /etc/mas-relay/username ]; then
|
||||
RELAY_ARGS="--relay $MAS_RELAY --relay-credentials /etc/mas-relay"
|
||||
fi
|
||||
/app/integrations/postfix/install.sh --domain "$DOMAIN" \
|
||||
--statute "$STATUTE_DIR/.well-known/mas" --app-url http://127.0.0.1:8024/mas-events --auth \
|
||||
$RELAY_ARGS
|
||||
|
||||
# Deployment specifics.
|
||||
postconf -e \
|
||||
"maillog_file=/dev/stdout" "myhostname=$DOMAIN" "mydomain=$DOMAIN" \
|
||||
"mydestination=$DOMAIN" "relay_domains=" "mynetworks=127.0.0.0/8" \
|
||||
"smtpd_relay_restrictions=permit_mynetworks,check_recipient_access regexp:/etc/postfix/mas_rcpt.regexp,reject" \
|
||||
"local_transport=mas" "local_recipient_maps=" "mas_destination_recipient_limit=1" \
|
||||
"recipient_delimiter=+" "swap_bangpath=no" "allow_percent_hack=no" \
|
||||
"allow_untrusted_routing=yes" \
|
||||
"disable_vrfy_command=yes" "smtpd_helo_required=yes" "smtpd_recipient_limit=20" \
|
||||
"message_size_limit=10240000" "smtpd_banner=\$myhostname ESMTP MAS live test" \
|
||||
"maillog_file=/dev/stdout" "message_size_limit=10240000" \
|
||||
"smtpd_banner=\$myhostname ESMTP MAS live test" \
|
||||
"smtpd_tls_security_level=may" \
|
||||
"smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem" \
|
||||
"smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key" \
|
||||
|
|
@ -46,74 +40,16 @@ postconf -e \
|
|||
# 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.
|
||||
# The ticket service sends replies only with a relay, capped at MAS_MAX_SENT.
|
||||
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"
|
||||
# Postfix's own bounces (null sender) never use the relay, so the ticket
|
||||
# service cap below really bounds everything sent through TEM.
|
||||
postconf -e "sender_dependent_default_transport_maps=inline:{<>=discard:}" \
|
||||
"empty_address_default_transport_maps_lookup_key=<>"
|
||||
SMTP_ARG="--smtp 127.0.0.1:25 --max-sent ${MAS_MAX_SENT:-300} --require-dmarc-pass"
|
||||
else
|
||||
postconf -e "default_transport=error:5.7.1 outbound mail disabled"
|
||||
fi
|
||||
|
||||
# Sender authentication (spec 14 item 10). The MTA does SPF/DKIM/DMARC, since
|
||||
# they need DNS; the gateway only reads Authentication-Results carrying our
|
||||
# authserv-id. Postfix header_checks drop incoming A-R headers claiming that
|
||||
# id; header_checks do not see milter-added headers, so only the milters'
|
||||
# own results survive and a sender cannot forge a pass. (OpenDKIM's
|
||||
# RemoveARFrom did not remove them in testing.) Annotate only: never reject,
|
||||
# and accept mail if a milter is down. Loopback (the ticket service's
|
||||
# replies) is skipped. Both milters run in the foreground: daemonized,
|
||||
# OpenDKIM died on its first connection in this container.
|
||||
mkdir -p /run/opendkim /run/opendmarc
|
||||
chown opendkim:opendkim /run/opendkim && chown opendmarc:opendmarc /run/opendmarc
|
||||
printf '127.0.0.1\n' > /etc/opendkim-peers
|
||||
cat > /etc/opendkim.conf <<CONF
|
||||
Mode v
|
||||
Socket inet:8891@127.0.0.1
|
||||
AuthservID $DOMAIN
|
||||
PeerList /etc/opendkim-peers
|
||||
UserID opendkim
|
||||
PidFile /run/opendkim/opendkim.pid
|
||||
Syslog no
|
||||
CONF
|
||||
cat > /etc/opendmarc.conf <<CONF
|
||||
Socket inet:8893@127.0.0.1
|
||||
AuthservID $DOMAIN
|
||||
TrustedAuthservIDs $DOMAIN
|
||||
SPFSelfValidate true
|
||||
SPFIgnoreResults true
|
||||
RejectFailures false
|
||||
PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat
|
||||
UserID opendmarc
|
||||
PidFile /run/opendmarc/opendmarc.pid
|
||||
Syslog false
|
||||
CONF
|
||||
# Milters in the foreground: daemonized, OpenDKIM died on its first
|
||||
# connection in this container.
|
||||
opendkim -f -x /etc/opendkim.conf &
|
||||
opendmarc -f -c /etc/opendmarc.conf &
|
||||
printf '/^Authentication-Results:[[:space:]]*%s([[:space:];]|$)/ IGNORE\n' "$DOMAIN_RE" \
|
||||
> /etc/postfix/ar_strip.regexp
|
||||
postconf -e "header_checks=regexp:/etc/postfix/ar_strip.regexp" "smtpd_milters=inet:127.0.0.1:8891,inet:127.0.0.1:8893" "non_smtpd_milters=" \
|
||||
"milter_default_action=accept" "milter_protocol=6"
|
||||
|
||||
cat >> /etc/postfix/master.cf <<CF
|
||||
mas unix - n n - - pipe
|
||||
flags=Rq user=mas argv=/usr/local/bin/mas-gateway
|
||||
--statute $STATUTE_DIR/.well-known/mas --trusted-authserv-id $DOMAIN
|
||||
pipe --app-url http://127.0.0.1:8024/mas-events --recipient \${recipient}
|
||||
CF
|
||||
|
||||
mkdir -p /var/lib/mas && chown mas /var/lib/mas
|
||||
# Replies are only recorded in the outbox unless a relay is configured above.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue