From 0a286ccc1c0dff37e0dbda47c78cb7cb8670d374 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Fri, 11 Aug 2023 12:22:29 -0700 Subject: [PATCH] Further testing and refinement completed. --- README.md | 8 ++++++++ defaults/main.yml | 12 ++++++------ docs/DEPLOYMENT.md | 38 ++++++++++++++++++++++++++++++------ docs/examples/backup.sh | 10 ++++++++++ docs/examples/print-rdata.py | 13 ++++++++++++ tasks/dovecot.yml | 10 +++++++++- tasks/maildir.yml | 18 ++++++++++++----- templates/dovecot.conf.j2 | 4 ++-- templates/main.cf.j2 | 4 ++-- vars/main.yml | 5 ++++- 10 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 docs/examples/backup.sh create mode 100755 docs/examples/print-rdata.py diff --git a/README.md b/README.md index e0b21cb..ff755a4 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,14 @@ Postfix `master.cf` should configure smtpd behavior to require encrypted client See [docs/CLIENTS.md](docs/CLIENTS.md) for notes on mail clients. +## Backups + +See the provided [example](docs/examples/backup.sh) script. Keep in mind that when restoring the `imap.passwd` file for Dovecot, that a new system will have different user ids for maildir. There is a helper to rewrite all the uid/gids to the maildir user when restoring from a backup on a new system: + +```bash +ansible-playbook -e 'force_dovecot_passwd_file_maildir_ids=yes' playbooks/mail.yml +``` + ## Misc There are some interesting mta implementations that may replace or compliment parts of this stack in the future: diff --git a/defaults/main.yml b/defaults/main.yml index e9983d8..ecd038d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -8,8 +8,6 @@ dkim_key_path: /etc/dkimkeys #postfix_hostname: "{{ dkim_selector }}.{{ postfix_domain }}" imap_bind_address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}" #postfix_maildir_user: maildir -postfix_virtual_uid: "{{ ansible_facts.getent_passwd.maildir[1] }}" -postfix_virtual_gid: "{{ ansible_facts.getent_group.maildir[1] }}" #postfix_inet_interfaces: all #postfix_mynetworks: # - 127.0.0.0/8 @@ -42,9 +40,6 @@ postfix_smtpd_client_message_rate_limit: 3 postfix_smtpd_client_new_tls_session_rate_limit: 3 postfix_smtpd_client_auth_rate_limit: 3 -# robertdebock.dovecot -dovecot_mailbox_location: "maildir:{{ postfix_virtual_mailbox_base }}/{{ postfix_domain }}/%n" - postfix_install: - postfix # - mailutils @@ -118,4 +113,9 @@ postfix_smtpd_sasl_path: private/auth postfix_virtual_mailbox_maps: /etc/postfix/vmailbox #postfix_virtual_uid: 1000 #postfix_virtual_gid: 1000 -postfix_maildir_user: maildir \ No newline at end of file +postfix_maildir_user: maildir + +force_dovecot_passwd_file_maildir_ids: false # useful when recovering from backup + +# robertdebock.dovecot +dovecot_mailbox_location: "maildir:{{ postfix_virtual_mailbox_base }}/{{ postfix_domain }}/%n" diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 20bccaf..6f1ab21 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -1,6 +1,15 @@ # Mail Server: Deployment -1. Create MX and TXT records +1. Create MX and TXT records. For example, here are example records defined in dnscontrol: + ```Javascript + D('example.com', REG_NAMECHEAP, DnsProvider(DSP_NAMECHEAP), + A('mail', '10.87.129.99'), + MX('@', 10, 'mail.example.com.'), + TXT('_dmarc', 'v=DMARC1; p=none'), + TXT('@', 'v=spf1 mx ~all') + ); + ``` + The `A` and `MX` records are required, while the `TXT` records are optional but recommended. 2. Set a password for the "main" virtual inbox: @@ -8,11 +17,17 @@ echo main:$(doveadm pw -s BLF-CRYPT) >> files/$TARGET/imap.passwd ``` + Also, if you use `doas` rather than `sudo`, you need to permit your ansible_user to become opendkim in your `/etc/doas.conf`: + + ``` + permit nopass blee as opendkim + ``` + 3. Copy a vars/targets file, update the values, and run this playbook - Sanity check opendkim (may need restart): + Troubleshooting: Sanity check opendkim (may need restart, although I think I fixed that): ```shell - l /var/spool/postfix/opendkim/opendkim.sock + ls -AlF /var/spool/postfix/opendkim/opendkim.sock ``` 4. look at the maildir uid/gid in main.cf and use those in the imap.passwd file (switching to the dovecot role will fix that later) @@ -29,9 +44,20 @@ 7. (optional) Create another TXT record for DKIM using the contents of /etc/dkimkeys/mail.txt - * See [scripts/print-rdata.py](../scripts/print-rdata.py) for an example of how to parse mail.txt - * See [octodns](https://github.com/octodns/octodns-easydns) and [dnscontrol](https://dnscontrol.org/) + Here's an example line in dnscontrol: + ```Javascript + TXT('mail._domainkey', 'v=DKIM1; h=sha256; k=rsa; s=email; p=MIIBIjANB...QIDAQAB') + ``` -8. (optional) After records propogate, verify outbound mail using: https://www.mail-tester.com/ + * See [print-rdata.py](examples/print-rdata.py) for a (kind of bad) example of how to automatically parse mail.txt + * See [dnscontrol](https://dnscontrol.org/) as well as [octodns](https://github.com/octodns/octodns-easydns) + + If you're really feeling adventurous, you could even set up a proper dmarc address to replace the original placeholder TXT record. + + ```Javascript + TXT('_dmarc', 'v=DMARC1; p=reject; rua=mailto:dmarc@satstack.cloud; fo=1') + ``` + + After records propogate, verify outbound mail using [mail-tester](https://www.mail-tester.com/). diff --git a/docs/examples/backup.sh b/docs/examples/backup.sh new file mode 100644 index 0000000..c32f812 --- /dev/null +++ b/docs/examples/backup.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -x + +TARGET=mail.example.com + +mkdir -p $HOME/archive/${TARGET}/{dovecot,postfix} +rsync -tav root@${TARGET}:/etc/dovecot/imap.passwd $HOME/archive/${TARGET}/ +rsync -tav root@${TARGET}:/etc/postfix/virtual $HOME/archive/${TARGET}/postfix +rsync -tav root@${TARGET}:/etc/dkimkeys $HOME/archive/${TARGET}/ +rsync -tav root@${TARGET}:/var/vmail $HOME/archive/${TARGET}/ diff --git a/docs/examples/print-rdata.py b/docs/examples/print-rdata.py new file mode 100755 index 0000000..a1ccab7 --- /dev/null +++ b/docs/examples/print-rdata.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import zonefile_parser + +with open("mail.txt","r") as stream: + content = stream.read() + records = zonefile_parser.parse(content) + + for record in records: + print() + print(f"{record.name}:") + print(record.rdata['value']) + print() + diff --git a/tasks/dovecot.yml b/tasks/dovecot.yml index a0ea40d..f45c242 100644 --- a/tasks/dovecot.yml +++ b/tasks/dovecot.yml @@ -7,7 +7,7 @@ - name: Register Dovecot account database ansible.builtin.file: - path: /etc/dovecot/imap.passwd + path: "{{ dovecot_passwd_file }}" state: touch owner: root group: dovecot @@ -32,3 +32,11 @@ src: dovecot.conf.j2 dest: /etc/dovecot/local.conf notify: restart dovecot + +- name: Update UID and GID in imap.passwd + ansible.builtin.replace: + path: "{{ dovecot_passwd_file }}" + regexp: '(.*):(\d+):(\d+)$' + replace: '\1:{{ maildir_uid }}:{{ maildir_gid }}' + when: force_dovecot_passwd_file_maildir_ids + notify: restart dovecot \ No newline at end of file diff --git a/tasks/maildir.yml b/tasks/maildir.yml index 4d200f6..4d4feca 100644 --- a/tasks/maildir.yml +++ b/tasks/maildir.yml @@ -32,12 +32,20 @@ system: true append: true -- name: Get maildir user's id +- name: Add maildir user's id and group id to ansible_facts ansible.builtin.getent: database: passwd key: "{{ postfix_maildir_user }}" -- name: Get maildir user's group id - ansible.builtin.getent: - database: group - key: "{{ postfix_maildir_user }}" +#- name: Add maildir user's id and group id to ansible_facts +# ansible.builtin.getent: +# database: "{{ item }}" +# key: "{{ postfix_maildir_user }}" +# loop: +# - passwd +# - group + +- name: Set maildir UID and GID + set_fact: + maildir_uid: "{{ ansible_facts.getent_passwd[postfix_maildir_user][1] }}" + maildir_gid: "{{ ansible_facts.getent_passwd[postfix_maildir_user][2] }}" diff --git a/templates/dovecot.conf.j2 b/templates/dovecot.conf.j2 index 375c839..f89bbbf 100644 --- a/templates/dovecot.conf.j2 +++ b/templates/dovecot.conf.j2 @@ -21,7 +21,7 @@ service imap-login { # doveadm pw -s BLF-CRYPT passdb { driver = passwd-file - args = username_format=%n scheme=blf-crypt /etc/dovecot/imap.passwd + args = username_format=%n scheme=blf-crypt {{ dovecot_passwd_file }} auth_verbose=yes } @@ -30,7 +30,7 @@ passdb { userdb { driver = passwd-file - args = username_format=%n /etc/dovecot/imap.passwd + args = username_format=%n {{ dovecot_passwd_file }} default_fields = uid={{ postfix_maildir_user }} gid={{ postfix_maildir_user }} # override_fields = diff --git a/templates/main.cf.j2 b/templates/main.cf.j2 index babb21d..e9568ec 100644 --- a/templates/main.cf.j2 +++ b/templates/main.cf.j2 @@ -17,8 +17,8 @@ virtual_mailbox_base = {{ postfix_virtual_mailbox_base }}/{{ postfix_domain }} virtual_mailbox_maps = {{ postfix_default_database_type }}:{{ postfix_virtual_mailbox_maps }} virtual_mailbox_limit = 0 # User: {{ postfix_maildir_user }} -virtual_uid_maps = static:{{ postfix_virtual_uid }} -virtual_gid_maps = static:{{ postfix_virtual_gid }} +virtual_uid_maps = static:{{ maildir_uid }} +virtual_gid_maps = static:{{ maildir_gid }} virtual_alias_maps = {{ postfix_default_database_type }}:{{ postfix_virtual_aliases_file }} {% endif %} diff --git a/vars/main.yml b/vars/main.yml index 47013a1..96c5ae7 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -21,4 +21,7 @@ postfix_virtual_mailbox_file: /etc/postfix/vmailbox postfix_smtpd_tls_dh1024_param_file: /etc/ssh/dhaparams.pem # https://github.com/vdukhovni/postfix/blob/master/postfix/INSTALL -postfix_compatibility_level: 3.5 # Debian 11 \ No newline at end of file +postfix_compatibility_level: 3.5 # Debian 11 + +# dovecot +dovecot_passwd_file: /etc/dovecot/imap.passwd