From f63f8bf0a6405c5788a6a36212c8523315f204f3 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Sat, 22 Jul 2023 15:53:56 -0700 Subject: [PATCH] Initialize repo for alias mail role. --- .gitignore | 1 + LICENSE | 17 +++ README.md | 69 ++++++++++++ defaults/main.yml | 121 ++++++++++++++++++++ docs/CLIENTS.md | 13 +++ docs/DEPLOYMENT.md | 36 ++++++ files/header_checks | 5 + files/master.cf | 67 +++++++++++ handlers/main.yml | 25 +++++ meta/main.yml | 5 + requirements.yml | 8 ++ tasks/dovecot.yml | 34 ++++++ tasks/maildir.yml | 43 ++++++++ tasks/main.yml | 25 +++++ tasks/opendkim.yml | 35 ++++++ tasks/postfix.yml | 92 ++++++++++++++++ templates/create-email-alias.j2 | 13 +++ templates/dovecot.conf.j2 | 69 ++++++++++++ templates/main.cf.j2 | 190 ++++++++++++++++++++++++++++++++ templates/opendkim.conf.j2 | 52 +++++++++ vars/main.yml | 24 ++++ 21 files changed, 944 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 docs/CLIENTS.md create mode 100644 docs/DEPLOYMENT.md create mode 100644 files/header_checks create mode 100644 files/master.cf create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 requirements.yml create mode 100644 tasks/dovecot.yml create mode 100644 tasks/maildir.yml create mode 100644 tasks/main.yml create mode 100644 tasks/opendkim.yml create mode 100644 tasks/postfix.yml create mode 100644 templates/create-email-alias.j2 create mode 100644 templates/dovecot.conf.j2 create mode 100644 templates/main.cf.j2 create mode 100644 templates/opendkim.conf.j2 create mode 100644 vars/main.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ff54e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +archive \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4baac1e --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +MIT No Attribution License + +Copyright (c) 2023 Brian Lee + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7267d2f --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# Ansible Role: disposable-mail + +This is an Ansible role that sets up a [mail server](https://wiki.archlinux.org/title/Mail_server) by installing and configuring [postfix](https://www.postfix.org/), [dovecot](https://dovecot.org/), and opendkim. + +It is intended to facilitate using smtp and imap service with disposable mail aliases for a single user. It stores mail using Maildir, which is a simple plaintext format. The configuration uses unix sockets for inter-process communication and prefers strong encryption for network connections. The configured [header_checks](files/header_checks) filter out unnecessary postfix mail headers to limit leakage of personal information. + +This configuration is not intended to replace a user's primary personal email account. Do not use a disposable alias for important or sensitive accounts. Messages are by default stored in plaintext on your server (unless you've set up disk encryption separately). + +It includes a helper script to create new email aliases. You can create an alias to call it. + +```shell +alias addmail='ssh root@host create-email-alias' +``` +Usage: `addmail newservice` creates an alias to receive mail at newservice@example.com + +## Requirements + +* Debian/Ubuntu +* [robertdebock.dovecot](https://github.com/robertdebock/ansible-role-dovecot) +* oefenweb.postfix role, the [bleetube fork](https://github.com/bleetube/ansible-role-postfix) + +See [requirements.yml](requirements.yml) + +## Variables + +```yaml +postfix_domain: example.com +postfix_hostname: mail.example.com +postfix_smtpd_tls_cert_file: "" +postfix_smtpd_tls_key_file: "" +postfix_smtpd_tls_dh1024_param_file: "" +``` + +See the [default variables](defaults/main.yml). + +## Example Playbook + +```yaml +- hosts: mail + become: yes + roles: + - bleetube.mail +``` + +## Example Deployment + +See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) + +## Security + +For hardening, we recommend that network access to dovecot (TCP/993) be restricted to trusted IPs. See [cve details](https://www.cvedetails.com/vulnerability-list/vendor_id-6485/Dovecot.html). + +## Privacy + +Postfix `master.cf` should configure smtpd behavior to require encrypted client connections. In practice, this means figuring out what connection method for a given mail client that is going to work with a mail server that requires strong encryption. + +See [docs/CLIENTS.md](docs/CLIENTS.md) for notes on mail clients. + +## Misc + +There are some interesting mta implementations that may replace or compliment parts of this stack in the future: +* [simple-nixos-mailserver](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver) +* [maddy](https://github.com/foxcpp/maddy) (go) +* [jmap](https://github.com/stalwartlabs/jmap-server), [vsmtp](https://github.com/viridIT/vSMTP) (rust) +* [roundcube](https://roundcube.net/) (php) + +## Credit + +Thanks to [Mischa ter Smitten](https://blog.tersmitten.nl) for his work on the [ansible-postfix](https://github.com/Oefenweb/ansible-postfix) role. The postfix setup process is largely a modified version of that role. The relevant license and copyright notice can be found in [postfix.yml](tasks/postfix.yml). \ No newline at end of file diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..e9983d8 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,121 @@ +--- +postfix_domain: "" +postfix_virtual_mailbox_base: /var/disposable-mail + +postfix_smtpd_banner: "$myhostname ESMTP $mail_name ({{ postfix_domain }})" +dkim_selector: mail +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 + +#postfix_inet_protocols: ipv4 +postfix_mydestination: + - $myhostname + +#smtpd_tls_security_level: may +# https://www.postfix.org/SMTPD_ACCESS_README.html +postfix_smtpd_relay_restrictions: + - permit_mynetworks + - permit_sasl_authenticated + - reject_unauth_destination +postfix_smtpd_recipient_restrictions: + - permit_mynetworks + - permit_sasl_authenticated + - reject_unauth_destination + +postfix_raw_options: + # https://www.postfix.org/MILTER_README.html + - | + smtpd_milters = unix:opendkim/opendkim.sock + - | + non_smtpd_milters = $smtpd_milters + +postfix_smtpd_client_connection_count_limit: 5 +postfix_smtpd_client_connection_rate_limit: 30 +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 + - libsasl2-2 +# - sasl2-bin + - libsasl2-modules + +postfix_hostname: "{{ ansible_fqdn }}" +postfix_mailname: "{{ ansible_fqdn }}" + +postfix_default_database_type: hash +postfix_aliases: [] +postfix_virtual_aliases: [] +postfix_sender_canonical_maps: [] +postfix_sender_canonical_maps_database_type: "{{ postfix_default_database_type }}" +postfix_recipient_canonical_maps: [] +postfix_recipient_canonical_maps_database_type: "{{ postfix_default_database_type }}" +postfix_transport_maps: [] +postfix_transport_maps_database_type: "{{ postfix_default_database_type }}" +postfix_sender_dependent_relayhost_maps: [] +postfix_header_checks: [] +postfix_header_checks_database_type: regexp +postfix_generic: "{{ postfix_smtp_generic_maps }}" +postfix_smtp_generic_maps: [] +postfix_smtp_generic_maps_database_type: "{{ postfix_default_database_type }}" + +postfix_relayhost: '' +postfix_relayhost_mxlookup: false +postfix_relayhost_port: 587 +postfix_relaytls: false + +postfix_sasl_auth_enable: true +postfix_sasl_user: "postmaster@{{ ansible_domain }}" +postfix_sasl_password: "{{ lookup('ansible.builtin.env', 'POSTFIX_SASL_PASSWORD') }}" +postfix_sasl_security_options: noanonymous +postfix_sasl_tls_security_options: noanonymous +postfix_sasl_mechanism_filter: '' + +postfix_smtp_tls_security_level: encrypt +postfix_smtp_tls_wrappermode: false +postfix_smtp_tls_note_starttls_offer: true + +postfix_inet_interfaces: all +postfix_inet_protocols: all +#postfix_mydestination: +# - "{{ postfix_hostname }}" +# - localdomain +# - localhost +# - localhost.localdomain +postfix_mynetworks: + - 127.0.0.0/8 + - '[::ffff:127.0.0.0]/104' + - '[::1]/128' + +#postfix_smtpd_banner: '$myhostname ESMTP $mail_name' +postfix_disable_vrfy_command: true +postfix_message_size_limit: 10240000 + +postfix_smtpd_tls_cert_file: /etc/ssl/certs/ssl-cert-snakeoil.pem +postfix_smtpd_tls_key_file: /etc/ssl/private/ssl-cert-snakeoil.key +postfix_smtpd_tls_security_level: may +postfix_smtp_tls_CApath: /etc/ssl/certs +postfix_smtpd_sasl_type: dovecot +postfix_smtpd_sasl_security_options: noanonymous, noplaintext, forward_secrecy +# path outside of chroot: /var/spool/postfix/private/auth +postfix_smtpd_sasl_path: private/auth + +#postfix_raw_options: [] + +#postfix_virtual_mailbox_base: /var/mail +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 diff --git a/docs/CLIENTS.md b/docs/CLIENTS.md new file mode 100644 index 0000000..50f22f2 --- /dev/null +++ b/docs/CLIENTS.md @@ -0,0 +1,13 @@ +# Mail Clients + +This is where notes can be collected regarding various mail clients. + +* Most apps will only work using port 25, ie. smtp with STARTTLS +* Thunderbird only seems to work using port 465, ie. smtps with submissions + +For some light reading, see the [TLS](https://www.postfix.org/TLS_README.html) article on postfix.org. + +* smtp=25 +* smtps=submissions=465 +* submission=587 + diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..e303b0e --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,36 @@ +# Mail Server: Deployment + +1. Create MX and TXT records + +2. Set a password for the "main" virtual inbox: + + ```shell + echo main:$(doveadm pw -s BLF-CRYPT) >> files/$TARGET/imap.passwd + ``` + +3. Copy a vars/targets file, update the values, and run this playbook + + Sanity check opendkim (may need restart): + ```shell + l /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) + +5. configure some virtual aliases in /etc/postfix/virtual and run: + + ```shell + postmap virtual vmailbox + ``` + + See `man 5 postconf` for details. + +6. 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/) + +7. Sanity check: https://mxtoolbox.com/ + +8. After records propogate, verify outbound mail using: https://www.mail-tester.com/ + diff --git a/files/header_checks b/files/header_checks new file mode 100644 index 0000000..047e268 --- /dev/null +++ b/files/header_checks @@ -0,0 +1,5 @@ +/^Received:.*/ IGNORE +/^X-Originating-IP:/ IGNORE +/^X-Mailer:/ IGNORE +/^Mime-Version:/ IGNORE +/^User-Agent:/ IGNORE \ No newline at end of file diff --git a/files/master.cf b/files/master.cf new file mode 100644 index 0000000..124e4eb --- /dev/null +++ b/files/master.cf @@ -0,0 +1,67 @@ +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +smtp inet n - y - - smtpd +#smtp inet n - y - 1 postscreen +#smtpd pass - - y - - smtpd +#dnsblog unix - - y - 0 dnsblog +#tlsproxy unix - - y - 0 tlsproxy +submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_tls_auth_only=yes + -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions + -o smtpd_recipient_restrictions= + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o milter_macro_daemon_name=ORIGINATING +submissions inet n - y - - smtpd + -o syslog_name=postfix/smtps + -o smtpd_tls_wrappermode=yes + -o smtpd_sasl_auth_enable=yes + -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions + -o smtpd_recipient_restrictions= + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o milter_macro_daemon_name=ORIGINATING +#628 inet n - y - - qmqpd +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +relay unix - - y - - smtp + -o syslog_name=postfix/$service_name +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +postlog unix-dgram n - n - 1 postlogd diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..3b78f8f --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,25 @@ +--- +- name: reload dovecot + service: name=dovecot state=reloaded + +- name: restart dovecot + service: name=dovecot state=restarted + +- name: restart opendkim + service: name=opendkim state=restarted + +- name: restart postfix + service: name=postfix state=restarted + +- name: reload postfix + service: name=postfix@- state=reload + +- name: new virtual mailboxes + ansible.builtin.command: + chdir: /etc/postfix + cmd: postmap vmailbox + +- name: new virtual mail aliases + ansible.builtin.command: + chdir: /etc/postfix + cmd: postmap virtual diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..04f6005 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,5 @@ +--- +#dependencies: [] +dependencies: + - robertdebock.dovecot +# - oefenweb.postfix \ No newline at end of file diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..a5929bc --- /dev/null +++ b/requirements.yml @@ -0,0 +1,8 @@ +--- +roles: + - src: https://github.com/robertdebock/ansible-role-dovecot + name: robertdebock.dovecot + +# - src: https://github.com/bleetube/ansible-role-postfix +# name: oefenweb.postfix +# version: virtual \ No newline at end of file diff --git a/tasks/dovecot.yml b/tasks/dovecot.yml new file mode 100644 index 0000000..a0ea40d --- /dev/null +++ b/tasks/dovecot.yml @@ -0,0 +1,34 @@ +--- +- name: Remove default include path to manage configuration via a single template. + ansible.builtin.lineinfile: + path: /etc/dovecot/dovecot.conf + regexp: '^!include conf.d' + line: '#!include conf.d/*.conf' + +- name: Register Dovecot account database + ansible.builtin.file: + path: /etc/dovecot/imap.passwd + state: touch + owner: root + group: dovecot + mode: '0640' + changed_when: false + +- name: Ensure mail_home directory (Dovecot) + ansible.builtin.file: + name: /srv/mail + owner: dovecot + group: dovecot + state: directory + mode: '0770' + +- name: Ensure local configuration file is included from dovecot.conf + ansible.builtin.lineinfile: + path: /etc/dovecot/dovecot.conf + line: '!include_try local.conf' + +- name: Configure Dovecot + ansible.builtin.template: + src: dovecot.conf.j2 + dest: /etc/dovecot/local.conf + notify: restart dovecot diff --git a/tasks/maildir.yml b/tasks/maildir.yml new file mode 100644 index 0000000..4d200f6 --- /dev/null +++ b/tasks/maildir.yml @@ -0,0 +1,43 @@ +--- +- name: Get nologin path for maildir user + ansible.builtin.find: + paths: + - /bin + - /sbin + - /usr/bin + - /usr/sbin + patterns: nologin + register: nologin_bin + +- name: Create maildir group + ansible.builtin.group: + name: "{{ postfix_maildir_user }}" + state: present + +- name: Ensure maildir directory + ansible.builtin.file: + name: "{{ postfix_virtual_mailbox_base }}/{{ postfix_domain }}" + owner: root + group: "{{ postfix_maildir_user }}" + state: directory + mode: '0770' + +- name: Create maildir user + ansible.builtin.user: + shell: "{{ nologin_bin.files[0].path }}" + home: "{{ postfix_virtual_mailbox_base }}" + name: "{{ postfix_maildir_user }}" + group: "{{ postfix_maildir_user }}" + groups: mail + system: true + append: true + +- name: Get maildir user's id + 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 }}" diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..e09228d --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,25 @@ +--- +#- name: Assert all required variables have been defined. +# ansible.builtin.assert: +# that: +# - item is defined +# - item != '' +# fail_msg: "FAILED: Required mail settings are not configured." +# quiet: true +# loop: + +- name: Set up Maildir + import_tasks: maildir.yml + tags: maildir, postfix + +- name: Set up Postfix + import_tasks: postfix.yml + tags: postfix + +- name: Install OpenDKIM and generate keys + import_tasks: opendkim.yml + tags: dkim + +- name: Install and configure Dovecot + import_tasks: dovecot.yml + tags: dovecot diff --git a/tasks/opendkim.yml b/tasks/opendkim.yml new file mode 100644 index 0000000..74b6560 --- /dev/null +++ b/tasks/opendkim.yml @@ -0,0 +1,35 @@ +--- +- name: Install and update OpenDKIM + ansible.builtin.package: + name: + - opendkim + - opendkim-tools + state: latest + +- name: Configure OpenDKIM + ansible.builtin.template: + src: opendkim.conf.j2 + dest: /etc/opendkim.conf + notify: restart opendkim + +- name: Ensure OpenDKIM unix socket path for postfix + ansible.builtin.file: + path: /var/spool/postfix/opendkim + state: directory + owner: postfix + group: opendkim + mode: '0770' + notify: restart opendkim + +- name: Generate DKIM signing key + ansible.builtin.command: + cmd: "opendkim-genkey -r -s {{ dkim_selector }} -b 2048 -d {{ postfix_domain }} --directory /etc/dkimkeys" + creates: "/etc/dkimkeys/{{ dkim_selector }}.private" + become: true + become_user: opendkim + +- name: Ensure postfix is in opendkim group + ansible.builtin.user: + name: postfix + groups: opendkim + append: true diff --git a/tasks/postfix.yml b/tasks/postfix.yml new file mode 100644 index 0000000..bd73158 --- /dev/null +++ b/tasks/postfix.yml @@ -0,0 +1,92 @@ +--- +# Much of the postfix setup process was based on github.com/Oefenweb/ansible-postfix +# +# Copyright (c) Oefenweb.nl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is furnished +# to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +- name: Configure debconf + debconf: + name: "{{ item.name }}" + question: "{{ item.question }}" + value: "{{ item.value }}" + vtype: "{{ item.vtype }}" + with_items: "{{ postfix_debconf_selections }}" + +- name: Install and configure Postfix + ansible.builtin.package: + name: "{{ postfix_install }}" + +- name: Copy header checks + ansible.builtin.copy: + src: header_checks + dest: /etc/postfix/ + owner: root + group: root + mode: '0644' + notify: restart postfix + +- name: Configure Postfix master process + ansible.builtin.copy: + src: master.cf + dest: /etc/postfix/ + owner: root + group: root + mode: '0644' + notify: restart postfix + +- name: Configure Postfix main process + ansible.builtin.template: + src: main.cf.j2 + dest: /etc/postfix/main.cf + +- name: Configure virtual mailboxes + ansible.builtin.lineinfile: + path: /etc/postfix/vmailbox + regexp: '^main@{{ postfix_domain }}\s+main/' + line: 'main@{{ postfix_domain }} main/' + create: true + notify: new virtual mailboxes + +- name: Postmap the virtual addresses + ansible.builtin.lineinfile: + path: /etc/postfix/vmailbox + regexp: '^main@{{ postfix_domain }}\s+main/' + line: 'main@{{ postfix_domain }} main/' + notify: new virtual aliases + +- name: Flush handlers + ansible.builtin.meta: flush_handlers + +- name: Generate Diffie-Hellman parameters with the size recommended by postconf (2048 bits) + community.crypto.openssl_dhparam: + path: "{{ postfix_smtpd_tls_dh1024_param_file }}" + size: 2048 + +- name: Temporarily stop postfix + ansible.builtin.service: + name: postfix + state: stopped + enabled: false + +- name: Set up helper script to create new email aliases + ansible.builtin.template: + src: create-email-alias.j2 + dest: /usr/local/bin/create-email-alias + mode: '0755' \ No newline at end of file diff --git a/templates/create-email-alias.j2 b/templates/create-email-alias.j2 new file mode 100644 index 0000000..76a4c06 --- /dev/null +++ b/templates/create-email-alias.j2 @@ -0,0 +1,13 @@ +#!/bin/bash +if [ -z $1 ];then + echo "Usage: ${0} " + exit 1 +fi + +if grep "$1@{{ postfix_domain }}" /etc/postfix/virtual +then + echo "already exists" + exit 0 +fi +echo "$1@{{ postfix_domain }} main@{{ postfix_domain }}" >> /etc/postfix/virtual +postmap /etc/postfix/virtual \ No newline at end of file diff --git a/templates/dovecot.conf.j2 b/templates/dovecot.conf.j2 new file mode 100644 index 0000000..375c839 --- /dev/null +++ b/templates/dovecot.conf.j2 @@ -0,0 +1,69 @@ +# https://doc.dovecot.org/settings/core/ +auth_mechanisms = plain scram-sha-256 +protocols = imap +listen = {{ imap_bind_address }} + +# /usr/share/doc/dovecot/example-config/conf.d/10-master.conf +# disable plaintext imap +service imap-login { + inet_listener imap { + port = 0 + } + inet_listener imaps { + port = 993 + ssl = yes + } +} + +# authenticate using a hash in a password file +# https://doc.dovecot.org/configuration_manual/authentication/passwd_file/#authentication-passwd-file +# Generate a password: +# doveadm pw -s BLF-CRYPT +passdb { + driver = passwd-file + args = username_format=%n scheme=blf-crypt /etc/dovecot/imap.passwd + auth_verbose=yes +} + +# after authentication, use the system user account details +# https://doc.dovecot.org/configuration_manual/authentication/user_databases_userdb/#authentication-user-database +userdb { + driver = passwd-file + + args = username_format=%n /etc/dovecot/imap.passwd + + default_fields = uid={{ postfix_maildir_user }} gid={{ postfix_maildir_user }} +# override_fields = + +# skip = never +# result_failure = continue +# result_internalfail = continue +# result_success = return-ok + +# auth_verbose = default +} + +# IMAP +ssl=yes +ssl_cert=<{{ postfix_smtpd_tls_cert_file }} +ssl_key=<{{ postfix_smtpd_tls_key_file }} +ssl_dh=<{{ postfix_smtpd_tls_dh1024_param_file }} +#verbose_ssl=yes + +# SASL +service auth { + unix_listener /var/spool/postfix/private/auth { + mode = 0660 + user = postfix + group = postfix + } + +} + +# https://doc.dovecot.org/configuration_manual/home_directories_for_virtual_users/#ways-to-set-up-home-directory +# https://doc.dovecot.org/admin_manual/filesystem_permission/ +mail_location = maildir:{{ postfix_virtual_mailbox_base }}/{{ postfix_domain }}/%n +mail_home=/srv/mail/%Lu + +# https://doc.dovecot.org/admin_manual/logging/#dovecot-logging +#mail_debug=yes diff --git a/templates/main.cf.j2 b/templates/main.cf.j2 new file mode 100644 index 0000000..babb21d --- /dev/null +++ b/templates/main.cf.j2 @@ -0,0 +1,190 @@ +# {{ ansible_managed }} + +# See /usr/share/postfix/main.cf.dist for a commented, more complete version + +myorigin = $mydomain + +smtpd_banner = {{ postfix_smtpd_banner }} +biff = no + +{% if postfix_domain is defined %} +mydomain = {{ postfix_domain }} + +# https://www.postfix.org/VIRTUAL_README.html#virtual_mailbox +# https://doc.dovecot.org/configuration_manual/home_directories_for_virtual_users/#ways-to-set-up-home-directory +virtual_mailbox_domains = $mydomain +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_alias_maps = {{ postfix_default_database_type }}:{{ postfix_virtual_aliases_file }} +{% endif %} + +## Delivering Mail +# https://www.postfix.org/SMTPD_ACCESS_README.html +smtpd_tls_security_level = {{ postfix_smtpd_tls_security_level }} +smtp_tls_CApath = {{ postfix_smtp_tls_CApath }} + +# Anvil +# http://www.postfix.org/TUNING_README.html#conn_limit +smtpd_client_connection_count_limit = {{ postfix_smtpd_client_connection_count_limit }} +smtpd_client_connection_rate_limit = {{ postfix_smtpd_client_connection_rate_limit }} +smtpd_client_message_rate_limit = {{ postfix_smtpd_client_message_rate_limit }} +smtpd_client_new_tls_session_rate_limit = {{ postfix_smtpd_client_new_tls_session_rate_limit }} +smtpd_client_auth_rate_limit = {{ postfix_smtpd_client_auth_rate_limit }} +smtpd_client_event_limit_exceptions = $mynetworks + +{% if postfix_compatibility_level is defined %} +compatibility_level = {{ postfix_compatibility_level }} +{% endif %} + +# TLS parameters +# http://www.postfix.org/TLS_README.html +smtpd_tls_dh1024_param_file = {{ postfix_smtpd_tls_dh1024_param_file }} +{% if postfix_smtpd_tls_security_level != 'encrypt' %} +smtpd_tls_auth_only = yes +{% endif %} +smtpd_tls_cert_file = {{ postfix_smtpd_tls_cert_file }} +smtpd_tls_key_file = {{ postfix_smtpd_tls_key_file }} +smtpd_use_tls=yes +# https://www.postfix.org/postconf.5.html#smtpd_tls_session_cache_database +smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache +# https://weakdh.org/sysadmin.html +smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA +# https://www.postfix.org/SASL_README.html +smtpd_sasl_type = {{ postfix_smtpd_sasl_type }} +smtpd_sasl_path = {{ postfix_smtpd_sasl_path }} +{% if postfix_smtpd_client_restrictions is defined %} +smtpd_sasl_security_options = {{ postfix_smtpd_sasl_security_options }} +{% endif %} +smtpd_sasl_local_domain = $myhostname + +# https://github.com/Oefenweb/ansible-postfix/pull/123 +{% if postfix_smtp_tls_mandatory_ciphers is defined %} +smtp_tls_mandatory_ciphers = {{ postfix_smtp_tls_mandatory_ciphers }} +{% endif %} + +{% if postfix_smtpd_tls_mandatory_ciphers is defined %} +smtpd_tls_mandatory_ciphers = {{ postfix_smtpd_tls_mandatory_ciphers }} +{% endif %} + +{% if postfix_smtpd_tls_mandatory_protocols is defined %} +smtpd_tls_mandatory_protocols = {{ postfix_smtpd_tls_mandatory_protocols }} +{% endif %} + +{% if postfix_smtpd_tls_protocols is defined %} +smtpd_tls_protocols = {{ postfix_smtpd_tls_protocols }} +{% endif %} + +{% if postfix_smtp_tls_mandatory_protocols is defined %} +smtp_tls_mandatory_protocols = {{ postfix_smtp_tls_mandatory_protocols }} +{% endif %} + +{% if postfix_smtp_tls_protocols is defined %} +smtp_tls_protocols = {{ postfix_smtp_tls_protocols }} +{% endif %} + +{% if postfix_smtpd_tls_loglevel is defined %} +smtpd_tls_received_header = yes +smtpd_tls_loglevel = {{ postfix_smtpd_tls_loglevel }} +{% endif %} + +# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for +# information on enabling SSL in the smtp client. + +myhostname = {{ postfix_hostname }} +default_database_type = {{ postfix_default_database_type }} +alias_maps = {{ postfix_default_database_type }}:{{ postfix_aliases_file }} +alias_database = {{ postfix_default_database_type }}:{{ postfix_aliases_file }} +{% if postfix_virtual_aliases and postfix_domain is not defined %} +virtual_alias_maps = {{ postfix_default_database_type }}:{{ postfix_virtual_aliases_file }} +{% endif %} +{% if postfix_sender_canonical_maps %} +sender_canonical_maps = {{ postfix_sender_canonical_maps_database_type }}:{{ postfix_sender_canonical_maps_file }} +{% endif %} +{% if postfix_recipient_canonical_maps %} +recipient_canonical_maps = {{ postfix_recipient_canonical_maps_database_type }}:{{ postfix_recipient_canonical_maps_file }} +{% endif %} +{% if postfix_transport_maps %} +transport_maps = {{ postfix_transport_maps_database_type }}:{{ postfix_transport_maps_file }} +{% endif %} +{% if postfix_sender_dependent_relayhost_maps %} +sender_dependent_relayhost_maps = {{ postfix_default_database_type }}:{{ postfix_sender_dependent_relayhost_maps_file }} +{% endif %} +{% if postfix_smtp_generic_maps %} +smtp_generic_maps = {{ postfix_smtp_generic_maps_database_type }}:{{ postfix_smtp_generic_maps_file }} +{% endif %} +{% if postfix_header_checks %} +smtp_header_checks = {{ postfix_header_checks_database_type }}:{{ postfix_header_checks_file }} +{% endif %} +mydestination = {{ postfix_mydestination | join(', ') }} +mynetworks = {{ postfix_mynetworks | join(' ') }} +mailbox_size_limit = 0 +recipient_delimiter = + +{% if postfix_inet_interfaces is string %} +inet_interfaces = {{ postfix_inet_interfaces }} +{% else %} +inet_interfaces = {{ postfix_inet_interfaces | join(', ') }} +{% endif %} +{% if postfix_inet_protocols is string %} +inet_protocols = {{ postfix_inet_protocols }} +{% else %} +inet_protocols = {{ postfix_inet_protocols | join(', ') }} +{% endif %} + +{% if postfix_relayhost %} +{% if postfix_relayhost_mxlookup %} +relayhost = {{ postfix_relayhost }}:{{ postfix_relayhost_port }} +{% else %} +relayhost = [{{ postfix_relayhost }}]:{{ postfix_relayhost_port }} +{% endif %} +{% if postfix_sasl_auth_enable %} +smtp_sasl_auth_enable = {{ postfix_sasl_auth_enable | bool | ternary('yes', 'no') }} +smtp_sasl_password_maps = {{ postfix_default_database_type }}:{{ postfix_sasl_passwd_file }} +smtp_sasl_security_options = {{ postfix_sasl_security_options }} +smtp_sasl_tls_security_options = {{ postfix_sasl_tls_security_options }} +smtp_sasl_mechanism_filter = {{ postfix_sasl_mechanism_filter }} +{% endif %} +{% if postfix_relaytls %} +smtp_use_tls = {{ postfix_relaytls | bool | ternary('yes', 'no') }} +smtp_tls_security_level = {{ postfix_smtp_tls_security_level }} +smtp_tls_wrappermode = {{ postfix_smtp_tls_wrappermode | bool | ternary('yes', 'no') }} +smtp_tls_note_starttls_offer = {{ postfix_smtp_tls_note_starttls_offer | bool | ternary('yes', 'no') }} +{% if postfix_smtp_tls_cafile is defined %} +smtp_tls_CAfile = {{ postfix_smtp_tls_cafile }} +{% endif %} +{% endif %} +{% else %} +relayhost = +{% endif %} + +{% if postfix_smtpd_client_restrictions is defined %} +smtpd_client_restrictions = {{ postfix_smtpd_client_restrictions | join(', ') }} +{% endif %} +{% if postfix_smtpd_helo_restrictions is defined %} +smtpd_helo_restrictions = {{ postfix_smtpd_helo_restrictions | join(', ') }} +{% endif %} +{% if postfix_smtpd_sender_restrictions is defined %} +smtpd_sender_restrictions = {{ postfix_smtpd_sender_restrictions | join(', ') }} +{% endif %} +{% if postfix_smtpd_recipient_restrictions is defined %} +smtpd_recipient_restrictions = {{ postfix_smtpd_recipient_restrictions | join(', ') }} +{% endif %} +{% if postfix_smtpd_relay_restrictions is defined %} +smtpd_relay_restrictions = {{ postfix_smtpd_relay_restrictions | join(', ') }} +{% endif %} +{% if postfix_smtpd_data_restrictions is defined %} +smtpd_data_restrictions = {{ postfix_smtpd_data_restrictions | join(', ') }} +{% endif %} + +message_size_limit = {{ postfix_message_size_limit }} + +# Disable the SMTP VRFY command. This stops some techniques used to harvest email addresses. +disable_vrfy_command = {{ postfix_disable_vrfy_command | bool | ternary('yes', 'no') }} + +{% for raw_option in postfix_raw_options | default([]) %} +{{ raw_option }} +{% endfor %} diff --git a/templates/opendkim.conf.j2 b/templates/opendkim.conf.j2 new file mode 100644 index 0000000..52c0c30 --- /dev/null +++ b/templates/opendkim.conf.j2 @@ -0,0 +1,52 @@ +# This is a basic configuration for signing and verifying. It can easily be +# adapted to suit a basic installation. See opendkim.conf(5) and +# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete +# documentation of available configuration parameters. + +Syslog yes +SyslogSuccess yes +#LogWhy no + +# Common signing and verification parameters. In Debian, the "From" header is +# oversigned, because it is often the identity key used by reputation systems +# and thus somewhat security sensitive. +Canonicalization relaxed/simple +#Mode sv +#SubDomains no +OversignHeaders From + +# Signing domain, selector, and key (required). For example, perform signing +# for domain "example.com" with selector "2020" (2020._domainkey.example.com), +# using the private key stored in /etc/dkimkeys/example.private. More granular +# setup options can be found in /usr/share/doc/opendkim/README.opendkim. +Domain {{ postfix_domain }} +Selector {{ dkim_selector }} +KeyFile {{ dkim_key_path}}/{{ dkim_selector }}.private + +# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when +# using a local socket with MTAs that access the socket as a non-privileged +# user (for example, Postfix). You may need to add user "postfix" to group +# "opendkim" in that case. +UserID opendkim +UMask 007 + +# Socket for the MTA connection (required). If the MTA is inside a chroot jail, +# it must be ensured that the socket is accessible. In Debian, Postfix runs in +# a chroot in /var/spool/postfix, therefore a Unix socket would have to be +# configured as shown on the last line below. +#Socket local:/run/opendkim/opendkim.sock +#Socket inet:8891@localhost +#Socket inet:8891 +Socket local:/var/spool/postfix/opendkim/opendkim.sock + +PidFile /run/opendkim/opendkim.pid + +# Hosts for which to sign rather than verify, default is 127.0.0.1. See the +# OPERATION section of opendkim(8) for more information. +#InternalHosts 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12 + +# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided +# by the package dns-root-data. +TrustAnchorFile /usr/share/dns/root.key +#Nameservers 127.0.0.1 +SendReports no \ No newline at end of file diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..47013a1 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,24 @@ +# vars file +--- +postfix_debconf_selections: + - name: postfix + question: postfix/main_mailer_type + value: No configuration + vtype: select + +postfix_main_cf: /etc/postfix/main.cf +postfix_mailname_file: /etc/mailname +postfix_aliases_file: /etc/aliases +postfix_virtual_aliases_file: /etc/postfix/virtual +postfix_sasl_passwd_file: /etc/postfix/sasl_passwd +postfix_sender_canonical_maps_file: /etc/postfix/sender_canonical_maps +postfix_recipient_canonical_maps_file: /etc/postfix/recipient_canonical_maps +postfix_transport_maps_file: /etc/postfix/transport_maps +postfix_sender_dependent_relayhost_maps_file: /etc/postfix/sender_dependent_relayhost_maps +postfix_smtp_generic_maps_file: /etc/postfix/generic +postfix_header_checks_file: /etc/postfix/header_checks +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