Files
ansible-role-eom/tasks/mail.yaml
T
claudeandClaude Opus 5.5 507b2225b1 mail: relay outbound mail through Amazon SES
Gmail and other large providers refuse mail sent directly from our
residential IP. Send outbound mail through an SES SMTP relay configured
from the vault (mail_relay_host, mail_relay_user, mail_relay_password).
Unset values fall back to direct delivery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 15:36:15 -05:00

325 lines
10 KiB
YAML

---
# tasks file for mail
- name: Create DMS namespace
k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: mail
- name: Request a certificate for DMS
k8s:
state: present
definition:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: mail
namespace: mail
spec:
secretName: mail
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
duration: 2160h # 90d
renewBefore: 360h # 15d
isCA: false
usages:
- server auth
- client auth
subject:
organizations:
- EOM
commonName: eom.dev
dnsNames:
- eom.dev
- postfix.eom.dev
- dovecot.eom.dev
issuerRef:
name: ca-issuer
kind: ClusterIssuer
- name: Create a persistent volume claim for mail
k8s:
state: present
definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dkim
namespace: mail
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 512Mi
- name: Create a persistent volume claim for mail
k8s:
state: present
definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: config
namespace: mail
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
- name: Create a persistent volume claim for mail
k8s:
state: present
definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mail
namespace: mail
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Ti
- name: Create a ConfigMap for encryption
k8s:
state: present
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: certs
namespace: mail
data:
privkey.pem: "{{ mail_encryption_privkey }}"
pubkey.pem: "{{ mail_encryption_pubkey }}"
- name: Create a ConfigMap for Dovecot
k8s:
state: present
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: dovecot
namespace: mail
data:
10-encryption.conf: |
# Enables mail_crypt for all services (pop3, pop3, etc)
mail_plugins = $mail_plugins mail_crypt
plugin {
mail_crypt_global_private_key = </certs/privkey.pem
mail_crypt_global_public_key = </certs/pubkey.pem
mail_crypt_save_version = 2
}
- name: Create a deployment
k8s:
definition:
apiVersion: v1
kind: Deployment
metadata:
name: mail
namespace: mail
spec:
replicas: 1
selector:
matchLabels:
app: mail
template:
metadata:
labels:
app: mail
spec:
containers:
- name: mail
image: mailserver/docker-mailserver:15.1.0 # pinned: v17 renamed LDAP env vars and breaks this config
securityContext:
# `allowPrivilegeEscalation: true` is required to support SGID via the `postdrop`
# executable in `/var/mail-state` for Postfix (maildrop + public dirs):
# https://github.com/docker-mailserver/docker-mailserver/pull/3625
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
privileged: false
capabilities:
add:
# file permission capabilities
- CHOWN
- FOWNER
- MKNOD
- SETGID
- SETUID
- DAC_OVERRIDE
# network capabilities
- NET_ADMIN # needed for F2B
- NET_RAW # needed for F2B
- NET_BIND_SERVICE
# miscellaneous capabilities
- SYS_CHROOT
- KILL
drop: [ALL]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: certs
mountPath: /certs
- name: config
mountPath: /tmp/docker-mailserver
- name: dovecot
mountPath: /etc/dovecot/conf.d/10-encryption.conf
subPath: 10-encryption.conf
- name: ssl
mountPath: /etc/letsencrypt
- name: mail
mountPath: /var/mail
- name: dkim
mountPath: /tmp/docker-mailserver/opendkim
ports:
- containerPort: 25
- containerPort: 110
- containerPort: 143
- containerPort: 465
- containerPort: 587
- containerPort: 993
- containerPort: 995
env:
- name: OVERRIDE_HOSTNAME
value: "postfix.eom.dev"
- name: POSTMASTER_ADDRESS
value: "postfix@eom.dev"
- name: ENABLE_POP3
value: "1"
- name: ACCOUNT_PROVISIONER
value: "LDAP"
- name: LDAP_SERVER_HOST
value: "ldap://openldap.openldap.svc.cluster.local/"
- name: LDAP_SEARCH_BASE
value: "dc=eom,dc=dev"
- name: LDAP_BIND_DN
value: "cn=readonly,dc=eom,dc=dev"
- name: LDAP_BIND_PW
value: "{{ openldap_readonly_password }}"
- name: LDAP_QUERY_FILTER_DOMAIN
# Only mailbox users define local domains; member accounts carry external addresses (e.g. @gmail.com).
value: "(&(mail=*@%s)(memberOf=cn=Postfix Users,ou=Postfix,ou=Services,dc=eom,dc=dev))"
- name: LDAP_QUERY_FILTER_USER
value: "(&(mail=%s)(memberOf=cn=Postfix Users,ou=Postfix,ou=Services,dc=eom,dc=dev))"
- name: LDAP_QUERY_FILTER_ALIAS
value: "(&(objectClass=posixAccount)(mailAlias=%s))"
- name: LDAP_QUERY_FILTER_GROUP
value: "(&(objectClass=posixAccount)(mailGroupMember=%s))"
- name: LDAP_QUERY_FILTER_SENDERS
value: "(&(objectClass=posixAccount)(|(mail=%s)(mailAlias=%s)(mailGroupMember=%s)))"
# Outbound relay (Amazon SES): residential IPs are refused by Gmail et al.
# Leave mail_relay_host unset to deliver directly.
- name: DEFAULT_RELAY_HOST
value: "{{ mail_relay_host | default('') }}"
- name: RELAY_USER
value: "{{ mail_relay_user | default('') }}"
- name: RELAY_PASSWORD
value: "{{ mail_relay_password | default('') }}"
- name: SPOOF_PROTECTION
value: "1"
- name: DOVECOT_AUTH_BIND
value: "yes"
- name: DOVECOT_DEFAULT_PASS_SCHEME
value: "MD5-CRYPT"
- name: DOVECOT_USER_FILTER
value: "(&(objectClass=posixAccount)(uid=%n)(memberOf=cn=Dovecot Users,ou=Dovecot,ou=Services,dc=eom,dc=dev))"
- name: DOVECOT_PASS_ATTRS
value: "uid=user,userPassword=password"
- name: DOVECOT_USER_ATTRS
value: "=home=/var/mail/%{ldap:uid},=uid=%{ldap:uidNumber},=gid=%{ldap:gidNumber},=mail=maildir:~/Maildir"
- name: ENABLE_SASLAUTHD
value: "1"
- name: SASLAUTHD_MECHANISMS
value: "ldap"
- name: SASLAUTHD_LDAP_FILTER
value: "(mail=%U@eom.dev)"
- name: SSL_TYPE
value: "manual"
- name: SSL_CERT_PATH
value: "/etc/letsencrypt/tls.crt"
- name: SSL_KEY_PATH
value: "/etc/letsencrypt/tls.key"
volumes:
- name: certs
configMap:
name: certs
- name: ssl
secret:
secretName: mail
- name: config
persistentVolumeClaim:
claimName: config
- name: mail
persistentVolumeClaim:
claimName: mail
- name: dkim
persistentVolumeClaim:
claimName: dkim
- name: dovecot
configMap:
name: dovecot
- name: Expose deployment as a service
k8s:
definition:
apiVersion: v1
kind: Service
metadata:
name: mail
namespace: mail
spec:
selector:
app: mail
ports:
- port: 25
name: smtp
- port: 110
name: pop3
- port: 143
name: imap
- port: 465
name: esmtps
- port: 587
name: esmtp
- port: 993
name: imaps
- port: 995
name: pop3s
type: LoadBalancer
externalTrafficPolicy: Local
- name: onionservice for smtp
k8s:
definition:
apiVersion: tor.k8s.torproject.org/v1alpha2
kind: OnionService
metadata:
name: smtp
namespace: mail
spec:
version: 3
rules:
- port:
number: 25
backend:
service:
name: mail
port:
number: 25