fix(postfix): enable TLS and fix Postmark authentication

- Add Python script to extract certificates from Traefik acme.json
- Mount extracted certs to /etc/ssl in container for TLS support
- Enable smtpd_tls_security_level: may for incoming STARTTLS
- Remove failed_when: false on cert extraction to catch failures early
- Fix relayhost username to default to password (Postmark server token auth)
- Change default Postmark port from 2525 to 587 (blocked on some networks)
- Create SSL directory before extraction

Fixes: SMTP authentication failures and enables TLS for Authelia password reset
This commit is contained in:
Jeremie Fraeys 2026-03-06 14:25:10 -05:00
parent 64defbd528
commit 3e0e97a00c
No known key found for this signature in database
2 changed files with 50 additions and 7 deletions

View file

@ -5,12 +5,7 @@
- name: Read Postfix relay host
set_fact:
postfix_relayhost: "{{ POSTFIX_RELAYHOST | default(lookup('env', 'POSTFIX_RELAYHOST') | default('smtp.postmarkapp.com', true), true) }}"
postfix_relayhost_port: "{{ POSTFIX_RELAYHOST_PORT | default(lookup('env', 'POSTFIX_RELAYHOST_PORT') | default('2525', true), true) }}"
no_log: true
- name: Read Postfix relay host username
set_fact:
postfix_relayhost_username: "{{ POSTFIX_RELAYHOST_USERNAME | default(lookup('env', 'POSTFIX_RELAYHOST_USERNAME') | default('', true), true) }}"
postfix_relayhost_port: "{{ POSTFIX_RELAYHOST_PORT | default(lookup('env', 'POSTFIX_RELAYHOST_PORT') | default('587', true), true) }}"
no_log: true
- name: Read Postfix relay host password
@ -18,6 +13,11 @@
postfix_relayhost_password: "{{ POSTFIX_RELAYHOST_PASSWORD | default(lookup('env', 'POSTFIX_RELAYHOST_PASSWORD') | default('', true), true) }}"
no_log: true
- name: Read Postfix relay host username
set_fact:
postfix_relayhost_username: "{{ POSTFIX_RELAYHOST_USERNAME | default(lookup('env', 'POSTFIX_RELAYHOST_USERNAME') | default(postfix_relayhost_password, true), true) }}"
no_log: true
- name: Fail if Postfix relay host username/password pairing is invalid
fail:
msg: "POSTFIX_RELAYHOST_USERNAME and POSTFIX_RELAYHOST_PASSWORD must both be set, or both be empty"
@ -43,6 +43,42 @@
path: /opt/postfix
state: directory
- name: Create Postfix SSL directory
file:
path: /opt/postfix/ssl
state: directory
- name: Extract certificate from Traefik acme.json
shell: |
set -euo pipefail
ACME_FILE="/opt/traefik/letsencrypt/acme.json"
DEST_DIR="/opt/postfix/ssl"
DOMAIN="{{ auth_hostname }}"
if [ -f "$ACME_FILE" ]; then
python3 << 'PYEOF'
import json, base64, sys
with open("/opt/traefik/letsencrypt/acme.json") as f:
data = json.load(f)
for resolver in data.values():
if "Certificates" in resolver:
for cert in resolver["Certificates"]:
if cert.get("domain", {}).get("main") == "{{ auth_hostname }}":
with open("/opt/postfix/ssl/tls.crt", "wb") as f:
f.write(base64.b64decode(cert["certificate"]))
with open("/opt/postfix/ssl/tls.key", "wb") as f:
f.write(base64.b64decode(cert["key"]))
print("Certificates extracted successfully")
sys.exit(0)
print("Domain not found in acme.json")
sys.exit(1)
PYEOF
chmod 644 "$DEST_DIR/tls.crt"
chmod 600 "$DEST_DIR/tls.key"
fi
args:
executable: /bin/bash
- name: Copy Docker Compose file for Postfix
template:
src: docker-compose.yml.j2

View file

@ -10,18 +10,25 @@ services:
{% endif %}
{% endif %}
POSTFIX_smtp_tls_security_level: "{{ postfix_smtp_tls_security_level }}"
POSTFIX_smtpd_tls_security_level: none
POSTFIX_smtpd_tls_security_level: may
POSTFIX_smtpd_tls_cert_file: /etc/ssl/tls.crt
POSTFIX_smtpd_tls_key_file: /etc/ssl/tls.key
POSTFIX_smtpd_tls_loglevel: 1
POSTFIX_relay_domains: "*"
POSTFIX_smtpd_relay_restrictions: "permit_mynetworks,reject"
POSTFIX_smtpd_recipient_restrictions: "permit_mynetworks,reject_unauth_destination"
ALLOWED_SENDER_DOMAINS: "{{ postfix_allowed_sender_domains }},services"
ALLOW_EMPTY_SENDER_DOMAINS: "{{ postfix_allow_empty_sender_domains | ternary('true', 'false') }}"
POSTFIX_mynetworks: "127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
volumes:
- /opt/postfix/ssl:/etc/ssl:ro
ports:
- "25:25"
networks:
- proxy
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
proxy: