From d85cfa1693068666512b183ce9437faddda7de87 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 20 Apr 2025 00:11:26 -0700 Subject: Initiate operation email w SES --- playbooks/mail.yml | 7 ++ .../kanidm/templates/stacks/docker-compose.yml | 2 +- playbooks/roles/mail/tasks/main.yml | 67 ++++++++++++ .../roles/mail/templates/stacks/docker-compose.yml | 120 +++++++++++++++++++++ .../roles/mail/templates/volumes/data/.gitkeep | 0 .../roles/mail/templates/volumes/data/dms/.gitkeep | 0 .../volumes/data/dms/config/dovecot-ldap.conf | 11 ++ .../volumes/data/dms/config/user-patches.sh | 41 +++++++ .../templates/volumes/data/dms/mail-logs/.gitkeep | 0 .../templates/volumes/data/dms/mail-state/.gitkeep | 0 .../mail/templates/volumes/data/dms/vmail/.gitkeep | 0 .../mail/templates/volumes/data/roundcube/.gitkeep | 0 .../volumes/data/roundcube/config/.gitkeep | 0 .../volumes/data/roundcube/config/oauth2.inc.php | 19 ++++ .../volumes/data/roundcube/config/sieve.inc.php | 4 + .../templates/volumes/data/roundcube/db/.gitkeep | 0 .../templates/proxy/sites-enabled/mail.conf | 15 +++ .../roles/traefik/templates/stacks/traefik.yml | 6 ++ 18 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 playbooks/mail.yml create mode 100644 playbooks/roles/mail/tasks/main.yml create mode 100644 playbooks/roles/mail/templates/stacks/docker-compose.yml create mode 100644 playbooks/roles/mail/templates/volumes/data/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/dms/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/dms/config/dovecot-ldap.conf create mode 100755 playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh create mode 100644 playbooks/roles/mail/templates/volumes/data/dms/mail-logs/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/dms/mail-state/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/dms/vmail/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/roundcube/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/roundcube/config/.gitkeep create mode 100644 playbooks/roles/mail/templates/volumes/data/roundcube/config/oauth2.inc.php create mode 100644 playbooks/roles/mail/templates/volumes/data/roundcube/config/sieve.inc.php create mode 100644 playbooks/roles/mail/templates/volumes/data/roundcube/db/.gitkeep create mode 100644 playbooks/roles/outbound/templates/proxy/sites-enabled/mail.conf (limited to 'playbooks') diff --git a/playbooks/mail.yml b/playbooks/mail.yml new file mode 100644 index 0000000..73c6d20 --- /dev/null +++ b/playbooks/mail.yml @@ -0,0 +1,7 @@ +--- + +- name: mail setup + hosts: mail + become: true + roles: + - mail diff --git a/playbooks/roles/kanidm/templates/stacks/docker-compose.yml b/playbooks/roles/kanidm/templates/stacks/docker-compose.yml index 7f568e8..7f8bfe2 100644 --- a/playbooks/roles/kanidm/templates/stacks/docker-compose.yml +++ b/playbooks/roles/kanidm/templates/stacks/docker-compose.yml @@ -3,7 +3,7 @@ services: image: kanidm/server volumes: - {{ kanidm_base }}/volumes/data:/data - - {{ traextor_base }}/volumes/certs/letsencrypt:/certs:ro + - {{ letsencrypt_certs }}:/certs:ro networks: - proxy {% if homelab_build %} diff --git a/playbooks/roles/mail/tasks/main.yml b/playbooks/roles/mail/tasks/main.yml new file mode 100644 index 0000000..12b789d --- /dev/null +++ b/playbooks/roles/mail/tasks/main.yml @@ -0,0 +1,67 @@ +--- + +- name: Build mail compose dirs + ansible.builtin.file: + state: directory + dest: '{{ mail_base }}/{{ item.path }}' + owner: 1000 + group: 1000 + mode: 0755 + with_filetree: '../templates' + when: item.state == 'directory' + +- name: Build mail compose files + ansible.builtin.template: + src: '{{ item.src }}' + dest: '{{ mail_base }}/{{ item.path }}' + owner: 1000 + group: 1000 + mode: 0755 + with_filetree: '../templates' + when: item.state == 'file' + +# https://github.com/docker-mailserver/docker-mailserver/blob/23bb1c8e50dad1462c645b8a9cf50aeee8bc2625/Dockerfile#L149C19-L149C20 +- name: Build DMS compose dirs + ansible.builtin.file: + state: directory + dest: '{{ mail_base }}/volumes/data/dms/{{ item.path }}' + owner: 5000 + group: 5000 + mode: 0755 + with_filetree: '../templates/volumes/data/dms' + when: item.state == 'directory' + +- name: Build DMS template files with correct UID for docker mailserver + ansible.builtin.template: + src: '{{ item.src }}' + dest: '{{ mail_base }}/volumes/data/dms/{{ item.path }}' + owner: 5000 + group: 5000 + mode: 0755 + with_filetree: '../templates/volumes/data/dms' + when: item.state == 'file' + +- name: Build Roundcube compose dirs + ansible.builtin.file: + state: directory + dest: '{{ mail_base }}/volumes/data/roundcube/{{ item.path }}' + mode: 0755 + # https://github.com/roundcube/roundcubemail-docker/blob/ef4b8cc59eecbf0e25c66c7f3c464594cc310761/apache/Dockerfile#L145 + owner: 33 + group: 33 + with_filetree: '../templates/volumes/data/roundcube' + when: item.state == 'directory' + +- name: Build Roundcube template files + ansible.builtin.template: + src: '{{ item.src }}' + dest: '{{ mail_base }}/volumes/data/roundcube/{{ item.path }}' + owner: 33 + group: 33 + mode: 0755 + with_filetree: '../templates/volumes/data/roundcube' + when: item.state == 'file' + +- name: Deploy mail stack + ansible.builtin.command: + cmd: 'docker stack deploy -c {{ mail_base }}/stacks/docker-compose.yml mail' diff --git a/playbooks/roles/mail/templates/stacks/docker-compose.yml b/playbooks/roles/mail/templates/stacks/docker-compose.yml new file mode 100644 index 0000000..654f264 --- /dev/null +++ b/playbooks/roles/mail/templates/stacks/docker-compose.yml @@ -0,0 +1,120 @@ +services: + roundcube: + image: roundcube/roundcubemail:latest-nonroot + restart: always + volumes: + - {{ mail_base }}/volumes/data/roundcube/db:/var/roundcube/db + - {{ mail_base }}/volumes/data/roundcube/config:/var/roundcube/config + environment: + - ROUNDCUBEMAIL_DB_TYPE=sqlite + - ROUNDCUBEMAIL_SKIN=elastic + - ROUNDCUBEMAIL_PLUGINS={{ roundcube_plugins }} + - ROUNDCUBEMAIL_DEFAULT_HOST={{ roundcube_default_host }} + - ROUNDCUBEMAIL_DEFAULT_PORT={{ roundcube_default_port }} + - ROUNDCUBEMAIL_SMTP_SERVER={{ roundcube_smtp_host }} + - ROUNDCUBEMAIL_SMTP_PORT={{ roundcube_smtp_port }} + networks: + - proxy + - roundcube + deploy: + mode: replicated + replicas: 1 + labels: + - traefik.enable=true + - traefik.swarm.network=proxy + - traefik.http.routers.mail.tls=true + - traefik.http.routers.mail.tls.certResolver=letsencrypt + - traefik.http.routers.mail.rule=Host(`{{ mail_domain }}`) + - traefik.http.routers.mail.entrypoints=websecure + - traefik.http.services.mail.loadbalancer.server.port=8000 + + mailserver: + image: ghcr.io/docker-mailserver/docker-mailserver:latest + hostname: {{ mail_domain }} +{% if homelab_build %} + command: + - /bin/sh + - -c + - | + [ ! -f "/etc/letsencrypt/live/{{ mail_domain }}" ] && sleep 60 # Sleep until certificate requested from traefik + supervisord -c /etc/supervisor/supervisord.conf + healthcheck: + disable: true +{% endif %} + deploy: + mode: replicated + replicas: 1 + labels: + - traefik.enable=true + - traefik.swarm.network=proxy + # ManageSieve + - traefik.tcp.routers.sieve.tls.passthrough=true + - traefik.tcp.routers.sieve.rule=HostSNI(`*`) + - traefik.tcp.routers.sieve.entrypoints=sieve + - traefik.tcp.routers.sieve.service=sieve + - traefik.tcp.services.sieve.loadbalancer.server.port=4190 + # IMAP + - traefik.tcp.routers.imap.tls.passthrough=true + - traefik.tcp.routers.imap.rule=HostSNI(`*`) + - traefik.tcp.routers.imap.entrypoints=imap + - traefik.tcp.routers.imap.service=imap + - traefik.tcp.services.imap.loadbalancer.server.port=993 + # SMTP + - traefik.tcp.routers.smtp.tls.passthrough=true + - traefik.tcp.routers.smtp.rule=HostSNI(`*`) + - traefik.tcp.routers.smtp.entrypoints=smtp + - traefik.tcp.routers.smtp.service=smtp + - traefik.tcp.services.smtp.loadbalancer.server.port=465 + volumes: + - {{ mail_base }}/volumes/data/dms/vmail:/var/mail/ + - {{ mail_base }}/volumes/data/dms/mail-state:/var/mail-state/ + - {{ mail_base }}/volumes/data/dms/mail-logs:/var/log/mail/ + - {{ mail_base }}/volumes/data/dms/config:/tmp/docker-mailserver/ + - {{ mail_base }}/volumes/data/dms/config/dovecot-ldap.conf:/etc/dovecot/dovecot-ldap.conf.ext + - {{ letsencrypt_certs }}:/certs/:ro + - /etc/localtime:/etc/localtime:ro + environment: + - SSL_TYPE=manual + - SSL_CERT_PATH=/certs/{{ mail_domain }}.pem + - SSL_KEY_PATH=/certs/{{ mail_domain }}.key + - ENABLE_CLAMAV=0 + - ENABLE_AMAVIS=0 + - ENABLE_FAIL2BAN=1 + - ENABLE_SASLAUTHD=1 + - ENABLE_MANAGESIEVE=1 + - ENABLE_POSTGREY=0 + + - SPOOF_PROTECTION=1 + - ACCOUNT_PROVISIONER=LDAP + - LDAP_SERVER_HOST={{ ldap_server_host }} + - LDAP_SEARCH_BASE={{ ldap_search_base }} + - LDAP_BIND_DN={{ ldap_bind_dn }} + - LDAP_BIND_PW={{ email_ldap_api_token }} + + - LDAP_QUERY_FILTER_USER={{ ldap_query_filter_user }} + - LDAP_QUERY_FILTER_GROUP={{ ldap_query_filter_group }} + - LDAP_QUERY_FILTER_ALIAS={{ ldap_query_filter_alias }} + - LDAP_QUERY_FILTER_DOMAIN={{ ldap_query_filter_domain }} + - LDAP_QUERY_FILTER_SENDERS={{ ldap_query_filter_senders }} + + - POSTMASTER_ADDRESS={{ postmaster_email }} + + - SASLAUTHD_MECHANISMS=ldap + - SASLAUTHD_LDAP_FILTER={{ sasl_ldap_filter }} + + - ENABLE_OAUTH2=1 + - OAUTH2_INTROSPECTION_URL={{ roundcube_oauth2_user_uri }} + + - DEFAULT_RELAY_HOST={{ default_relay_host }} + - RELAY_USER={{ relay_user }} + - RELAY_PASSWORD={{ relay_password }} + + networks: + - mailserver + - proxy + +networks: + mailserver: + roundcube: + proxy: + external: true diff --git a/playbooks/roles/mail/templates/volumes/data/.gitkeep b/playbooks/roles/mail/templates/volumes/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/dms/.gitkeep b/playbooks/roles/mail/templates/volumes/data/dms/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/dms/config/dovecot-ldap.conf b/playbooks/roles/mail/templates/volumes/data/dms/config/dovecot-ldap.conf new file mode 100644 index 0000000..956942c --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/data/dms/config/dovecot-ldap.conf @@ -0,0 +1,11 @@ +base = {{ ldap_search_base }} +uris = {{ ldap_server_host }} +tls = no +ldap_version = 3 +default_pass_scheme = SSHA +dn = {{ ldap_bind_dn }} +dnpass = {{ email_ldap_api_token }} + +auth_bind = yes +auth_bind_userdn = {{ dovecot_auth_bind_userdn }} +user_filter = {{ dovecot_user_filter }} diff --git a/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh b/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh new file mode 100755 index 0000000..c62753f --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +postconf -e 'smtpd_sasl_type = dovecot' +postconf -e 'smtpd_sasl_path = /dev/shm/sasl-auth.sock' +postconf -e 'smtpd_sasl_auth_enable = yes' +postconf -e 'broken_sasl_auth_clients = yes' + +postconf -e 'smtp_tls_wrappermode = yes' # for relay + +# This is necessary for any users with multiple email addresses. Kanidm stores all email addresses in +# the mail attribute, which means that postfix clones emails to all addresses. Since dovecot only +# has a mailbox for the primary email address, the other addresses will bounce, and the sender +# will receive a bounce message. Kanidm provides the "primary" (first) email address in the +# emailprimary attribute, so we use that instead. +# +# We don't change the ldap-sender.cf or ldap-groups.cf files because we want people to be able to +# send mail from any of their email addresses, not just the primary one, and we want email to +# groups to be delivered regardless of which email it arrives at. +sed -i 's/result_attribute = mail/result_attribute = emailprimary/' /etc/postfix/ldap-aliases.cf +sed -i 's/result_attribute = mail/result_attribute = emailprimary/' /etc/postfix/ldap-domains.cf +sed -i 's/result_attribute = mail/result_attribute = emailprimary/' /etc/postfix/ldap-users.cf + +echo 'auth_username_format = %Ln' >> /etc/dovecot/conf.d/10-auth.conf + +echo 'username_format = %Ln' >> /etc/dovecot/dovecot-oauth2.conf.ext + +echo "passdb { + driver = ldap + args = /etc/dovecot/dovecot-ldap.conf.ext +} + +userdb { + driver = static + args = username_format=%u uid=docker gid=docker home=/var/mail/%d/%u + default_fields = uid=docker gid=docker home=/var/mail/%d/%u +}" > /etc/dovecot/conf.d/auth-ldap.conf.ext + +#userdb { +# driver = static +# args = home=/var/mail/%u +#}" diff --git a/playbooks/roles/mail/templates/volumes/data/dms/mail-logs/.gitkeep b/playbooks/roles/mail/templates/volumes/data/dms/mail-logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/dms/mail-state/.gitkeep b/playbooks/roles/mail/templates/volumes/data/dms/mail-state/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/dms/vmail/.gitkeep b/playbooks/roles/mail/templates/volumes/data/dms/vmail/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/roundcube/.gitkeep b/playbooks/roles/mail/templates/volumes/data/roundcube/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/roundcube/config/.gitkeep b/playbooks/roles/mail/templates/volumes/data/roundcube/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/roles/mail/templates/volumes/data/roundcube/config/oauth2.inc.php b/playbooks/roles/mail/templates/volumes/data/roundcube/config/oauth2.inc.php new file mode 100644 index 0000000..0284e9e --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/data/roundcube/config/oauth2.inc.php @@ -0,0 +1,19 @@ +