Email

Self-hosted email services (MDA, MTA). Setup focuses on running a secure mail server with the latest security options.

Ansible Role: mail

Role handles all steps that are provided in this documentation.

  • Highly Recommended to set mail_confirmation_prompt to true enabling interactive setup with user prompts for first time run. This will walk you through all required steps to configure the mail server. Subsequent runs can disable this to automatically apply the role.

  • Mail service should be placed on an isolated network as it is exposed to the world.

  • POP3 and non TLS/SSL connections should be considered compromised by default. If enabling remote MUA connections, Use TLS/SSL IMAP. Port 465 is a defacto standard for client email submissions before the creation of RFC 2476. It is safe to disable this unless you are forcing connections to this port. Read more about ports here.

  • Fail2ban may be used to mitigate excessive user logins. Use custom filters generated by extracting them from the mail docker image and adding as additional rules for fail2ban.

# Mail
A secure-by-default, principled, fullstack mailserver focusing on minimizing
required configuration options while providing high-domain reputation.

Configures Mail:
* Amavis
* Clamav
* Dovecot
* OpenDKIM
* OpenDMARC
* Pflogsumm
* Policyd-SPF
* Postfix
* Postgrey
* Postsrsd
* Postwhite
* Pyzor
* Razor

## Requirements
Audience is a user who simply wants to turnup a self-hosted, secure-by-default,
high-domain reputation non-docker mailserver with minimal fuss.

This does not intend to be a 'one role fits all' mail server solution. If you
need more complex setups (ldap, db, kerberos, etc), or are a business/company;
you are not the intended audience. Please fork (or make an offer :D).

DNS control is needed to setup correct DNS records. Configuration of these
options are guided during role deployment if the option is enabled.

Feedback is appreciated. Please submit feedback and bugs to issues.

Hardware Requirements:
* OS:   Debian 11 Bullseye
* RAM:  4GB
* CPU:  4 Threads (2 cores, 4 threads or 4 cores)
* Disk: 4GB Disk (+mail space)

Intended to be used with a total mail load below:
* ~ <30 mails per second
* ~ 500k mails/day

## Role Variables
Minimum Configuration.
  Defaults will work for a majority of users out of box. Settings have been
  throughly documented for usage.

The first domain listed is considered the primary domain, the first user
listed is considered the mail admin (receives postmaster@, abuse@, and
reports).

```yaml
mail_domains:
  - 'example.com'
  - 'example1.com'

mail_users:
  - {user: 'admin@example.com', pass: '{{ vault_user_admin_pass }}'}
  - {user: 'user@example1.com', pass: '{{ vault_user_pass }}'}
```

SSL Certificates must exist.

[defaults/main.yml](https://github.com/r-pufky/ansible_mail/blob/main/defaults/main/main.yml).

### Ports
All ports and protocols have been defined for the role.

Hosts should only define firewall rules for ports they need.

[defaults/ports.yml](https://github.com/r-pufky/ansible_mail/blob/main/defaults/main/ports.yml).

## Dependencies
N/A

## Example Playbook
Apply additional roles to support configuration needs as wanted. In this
example we configure the base debian system, configure certificates, apply this
mail role, and finally configure a firewall and fail2ban.

See defaults/main/ports.yml for a complete definition of ports used in this
role ready for firewall application.

host_vars/mail.example.com/vars/mail.yml
```yaml
mail_domains:
  - 'example.com'
  - 'example1.com'

mail_users:
  - {user: 'admin@example.com', pass: '{{ vault_user_admin_pass }}'}
  - {user: 'user@example1.com', pass: '{{ vault_user_pass }}'}
```

site.yaml
```yaml
- hosts: mail.example.com
  roles:
    - {debian-base-role}
    - {letsencrypt-cert-management-role}
    - mail
    - {firewall-role}
    - {fail2ban-role}
```

## Principals
Users just want mail.
  They don't want 3,000 options to fit all possible configurations, nor care
  about configuration minutiae. KISS/DRY principals apply for user settings.

Focus on a single task.
  Focus solely on secure mail delivery. Areas outside of mail delivery, such as
  certificate management, firewall configuration, and IP blocking should be
  left to tools which do those tasks better.

Tell the user what to do.
  Automate complexity. Users should not need to read RFC's, multiple tutorials,
  and sourceforge questions to configure required DNS settings for secure,
  high-domain reputation mail, such as SPF, DMARC, DKIM. Pick reasonable,
  secure defaults using user settings and explicitly guide them on what needs
  to be set outside of the mail server.

Document decisions.
  Users should be able to look at the code in the future and understand why
  decisions were made. Even 'trivial' decisions are lost without context after
  multiple years. Developers must document design decisions either in the task
  name or comments explaining the reasoning behind decisions.

Trust but verify.
  Users shoud not need to debug a configuration. Validate configuration within
  reason using tools to ensure mail configuration is correct.

## Issues
Create a bug and provide as much information as possible.

Associate pull requests with a submitted bug.

## License
[AGPL-3.0 License](https://github.com/r-pufky/ansible_mail/blob/main/LICENSE)

## Author Information
https://keybase.io/rpufky

None

Role Details: Updated: 2022-10-09 galaxy source service docs Reference Reference

Ports

---
###############################################################################
# Ports Configuration
###############################################################################
# Ports should be managed externally via an OS role.
#
# Reference:
# * https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
# * https://r-pufky.github.io/docs/services/email/mta/setup.html

ports:
  - {proto: 'tcp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 25,    comment: 'MTA smtp email submission'}
  - {proto: 'udp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 25,    comment: 'MTA smtp email submission'}
  - {proto: 'tcp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 143,   comment: 'imapv4'}
  - {proto: 'udp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 143,   comment: 'imapv4'}
  - {proto: 'tcp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 465,   comment: 'SSL MUA email submission (defacto standard)'}
  - {proto: 'tcp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 587,   comment: 'TLS MUA email submission (RFC 2476)'}
  - {proto: 'udp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 587,   comment: 'TLS MUA email submission (RFC 2476)'}
  - {proto: 'tcp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 993,   comment: 'imapv4 over TLS'}
  - {proto: 'udp', from_ip: 'any',       from_port: 'any', to_ip: '127.0.0.1', to_port: 993,   comment: 'imapv4 over TLS'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 8891,  comment: 'opendkim validation service'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 8893,  comment: 'opendmarc validation service'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 10001, comment: 'postsrsd forward lookup'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 10002, comment: 'postsrsd reverse lookup'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 10023, comment: 'postgrey check'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 10024, comment: 'amavis outgoing email queue'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 10025, comment: 'smtpd receiving from amavis incoming email queue'}
  - {proto: 'tcp', from_ip: '127.0.0.1', from_port: 'any', to_ip: '127.0.0.1', to_port: 10026, comment: 'amavis incoming email queue'}

Defaults

---
###############################################################################
# Mail Role Configuration
###############################################################################
# All options are required unless stated otherwise.
#
# Other default files:
# * ports.yml: defines all service ports, for use in firewall configuration.
#       no configuration needed.
# * users.yml: defines all user accounts. No configuration needed, unless
#       custom users/groups are wanted. They must be created before this role
#       is applied.

###############################################################################
# Mail Purge Install
###############################################################################
# During role setup, should existing mail configuration data be purged before
# installing packages and configuring? Required.
#
# No user data (e.g. actual mail) will be touched.
#
# Dataype: boolean (default: false)
# Special case: None

mail_purge_install: false

###############################################################################
# Mail Manual Confirmations
###############################################################################
# During role configuration sections where potential end-user interactions are
# required (e.g. DNS edits), should execution of role be paused until user
# confirms? Required.
#
# This will allow you to get exact DNS information to add, as well as review
# configuration settings before installing.
#
# Datatype: boolean (default: true)
# Special case: Recommend 'true' for the initial configuration to confirm DNS,
#               then 'false' afterwards for automatic role application.

mail_confirmation_prompt: true

###############################################################################
# Mail Domains
###############################################################################
# Domains to host email.
#
# The first domain listed is the 'primary domain'. A 'primary domain' is the
# domain email will originate from when the sender isn't fully specified (e.g.
# local accounts), as well as the mail server name.
#
# Datatype: list of strings (default: [])
# Special case: [] (domain will be determined from system hostname)

mail_domains: []

###############################################################################
# Mail MX Record
###############################################################################
# The MX Record to use for serving mail on 'mail_domains'.
#
# Typically 'mail.example.com'
#
# All hosted domains should have this defined.
#
# Configuration will automatically generate appropriate DNS records for you if
# none are defined, and provide instructions on how to setup DNS.
#
# Datatype: string (default: 'mail')
# Special case: None

mail_mx_record: 'mail'

###############################################################################
# Trusted Networks
###############################################################################
# Networks which are automatically trusted by the mail server.
#
# These should be minimized as no mail authentication will be required to send
# from these networks; these will also bypass DKIM,DMARC checks.
#
# Accepted formats:
# * IP (168.192.0.1)
# * CIDR (168.192.0.0/24)
# * Hostnames (desktop.example.com) (DMARC,DKIM only)
# * Wildcard hostnames (*.example.com) (DMARC,DKIM only)
#
# Datatype: list of strings (default: [])
# Special case:
# * localhost addresses
#     127.0.0.1, localhost, 127.0.0.0/8 will **always** be pre-pended to the
#     trusted network list. Do not add.
#
# * hostnames (example.com, *.example.com)
#     hostnames and wildcard hostnames will only be applied to DMARC, DKIM for
#     trusted hosts; not mail server (postfix).

mail_trusted_networks: []

###############################################################################
# OpenDKIM
###############################################################################
# OpenDKIM is an open source implementation of the DKIM (Domain Keys Identified
# Mail) sender authentication system. Used for validating authentic email and
# ensuring a high domain reputation. Required.
#
# Configuration will automatically generate keys for you if none are defined,
# and provide instructions on how to setup DNS. These keys should be added to
# vault so they are not regenerated every run. Keys are located:
#
#   /etc/opendkim/keys/{DOMAIN}/{SELECTOR}.{txt,private}
#
# Keys are automatically tested and validated against live DNS servers before
# allowing configuration to proceed.
#
# Datatype: list of dics (required after initial deployment)
#     {
#       domain: 'example.com',
#       selector: '{OPTIONAL default: "mail"}',
#       private: '{OPTIONAL first run}',
#       public: '{OPTIONAL first run}'
#     }
# Special case:
# * OPTIONAL
#     Optional values do not need to be defined until required.
#
# * mail
#     Both 'mail' and 'default' are well-known de-facto DKIM selectors. If you
#     don't know what these are, just use 'mail'.

mail_dkim_domains: []

###############################################################################
# Certificate Data
###############################################################################
# Location of SSL certificates to use.
#
# Certificate management is **not** managed in this role. Always point to the
# location of live certificates. This location must include 'fullchain.pem' and
# 'privkey.pem'.
#
# Typically let's encrypt certificates are here: '/etc/cert/live/{DOMAIN}'. Use
# primary domain certificates.
#
# Datatype: string (default: '/etc/ssl/certs/live/{{ mail_domains[0] }}')
# Special case:
# * /etc/ssl/certs/ssl-cert-snakeoil.pem
#     Use self-signed SSL certificates. These will force users to trust SSL
#     certificates and remove high-domain reputation. Only use for quickly
#     testing without setting up valid certificates. DO NOT USE LIVE.

mail_certs: '/etc/cert/live/{{ mail_domains[0] }}'

###############################################################################
# Mail Data
###############################################################################
# Location of mail data storage.
#
# If mail data is mounted, specify the mounted data location. This will be
# symlinked to /var/vmail for mail storage.
#
# Datatype: string (default: '' or '/var/vmail' -- will use /var/vmail)
# Special case: None

mail_data: '/var/vmail'

###############################################################################
# Logging Level
###############################################################################
# Amount of detail to log in all service logs.
#
# Applied to all services and translated to the equivalent numeric log level as
# needed. Increase logging level while debugging issues; keep at info or below
# for standard operation.
#
# Using debug or higher levels will potentially record private key material or
# passwords in logs. Increasing the log level will provide previous log level
# along with the new level of detail.
#
# Datatype: string (default: 'info')
# Special case:
# * critical
#     Only log critical messages.
# * error:
#     Only show error messages.
# * warn:
#     Only show warnings.
# * info:
#     Normal logging detail (default).
# * debug:
#     Include debugging information.
# * trace:
#     As much information as possible.

mail_log_level: 'info'

###############################################################################
# Detected Virus Autodelete
###############################################################################
# Delete emails containing viruses that were auto-detected.
#
# Measured in days.
#
# Datatype: integer (default: 7)
# Special case: 0 (detected emails will be kept indefinitely)

mail_virus_purge: 0

###############################################################################
# Postfix Reporting Interval
###############################################################################
# Emails status information, including potential errors/corrections for mail
# server.
#
# Datatype: string (default: 'weekly')
# Special case:
# * Accepted options are: annually, daily, hourly, monthly, weekly, yearly,
#                         reboot, disable
#
#     reboot: will send report on system reboot.
#     disable: disables reporting.

mail_report_interval: 'weekly'

###############################################################################
# Postfix Reporting Recipient
###############################################################################
# Defines who recieves the postfix reports.
#
# Datatype: string (default: '')
# Special case: '' (use primary domain postmaster address)
# empty: Postmaster address.

mail_report_recipient: ''

###############################################################################
# Mail Accounts
###############################################################################
# Mail user accounts for the mail server.
#
# These are virtual user accounts with no access to the underlying OS.
# Passwords should **not** be the same as any OS password.
#
# Datatype: list of dicts (requires minimum 1 user)
#     {
#       user: '{USER}@{DOMAIN}',
#       pass: '{HASH OR PASS}',
#       uid: '{OPTIONAL}',
#       gid: '{OPTIONAL},
#       gecos: '{OPTIONAL}',
#       home: '{OPTIONAL}',
#       shell: '{OPTIONAL}',
#       extra: '{OPTIONAL}'
#     }
# Special case:
# * primary mail user / first user:
#     The first defined user in 'mail_users' is considered to be the primary
#     user of the mail system. This user, by default, will receive reporting,
#     abuse, postmaster, and any other alerts a mail admin might care about.
#
# * OPTIONAL
#     Optional values do not need to be defined if unused.
#
# * pass:
#     Use vault to protect passwords. Plaintext passwords (anything not
#     detected as ARGON2ID) will automatically be hashed with ARGON2ID.
#
#     Using plaintext passwords will trigger password hash regeneration on
#     every ansible run.
#
#     ARGON2ID passwords may be generated using:
#
#       doveadm pw -s ARGON2ID

mail_users: []

###############################################################################
# Mail Aliases
###############################################################################
# Alias email addresses to 'mail_users'.
#
# Datatype: list of dicts (default: [])
#     {
#       'map': {FQDN EMAIL},
#       'to': {VALID USER FROM 'mail_users'}
#     }
# Special case:
# * Blackhole Email:
#     Accepts mail for delivery then immediately sends it to /dev/null
#     (discards).
#
#     Use to:'blackhole@localhost'.
#
# * Catch-all:
#     Catches all email not addressed to an existing user and redirects it to
#     a specific user.
#
#     Default: automatically create catch-all on all domains and send to first
#              user in 'mail_users'.
#     Custom: Use map:'@{DOMAIN}' at the end of aliases to redirect to a custom
#             user.
#     Disable: Use map:'@example.com' at the end of aliases to disable catch-all
#              for all domains.
#
#     A custom definition will disable autocreation of catch-alls for all
#     domains. Multiple custom definitions can be defined.
#
# * RFC 821,2142:
#     Require abuse@, postmaster@ on hosted domains for high domain reputation.
#
#     Default: abuse@, postmaster@ are autocreated for all hosted domains and
#              directed to the first user in 'mail_users'.
#
#     Custom: Use map:'abuse@{DOMAIN}' and map:'postmaster@{DOMAIN}' to
#             redirect to an alternative user.
#
#     A custom definition will disable autocreation for all domains. Define
#     **ALL** abuse@, postmaster@ addresses to minimize the risk of being
#     blacklisted.

mail_aliases: []

###############################################################################
# Map User Mailboxes to Another User
###############################################################################
# Nearly all cases should use aliases, see: 'mail_aliases'.
#
# Defined users will automatically have their mailboxes mapped. Only local
# accounts should be mapped here if needed; use 'mail_aliases'.
#
# Datatype: list of dicts (default: [])
#     {
#       'map': {FQDN EMAIL},
#       'to': {VALID USER FROM 'mail_users'}
#     }
# Special case: Never list a virtual mailbox mapping as a virtual alias, this
#               will result in undeliverable mail. Example:
#
#               mail_box_mappings:
#                 - {map:'mapped_user@example2.com', to:'user@example.com'}
#               mail_aliases:
#                 - {map:'mapped_user@example2.com', to:'user@example.com'}
#
#               This breaks delivery, don't do it!

mail_box_mappings: []

Users

---
###############################################################################
# User configuration
###############################################################################
# All specified users/groups must exist on the system (role will create these
# using defaults if they don't exist).
#
# Custom users/groups will be checked for existence and fail if they cannot be
# found.

mail_postfix_user:       'postfix'
mail_postfix_group:      'postfix'
mail_postsrsd_user:      'postsrsd'
mail_postsrsd_group:     'postsrsd'
mail_clamav_user:        'clamav'
mail_clamav_group:       'clamav'
mail_opendkim_user:      'opendkim'
mail_opendkim_group:     'opendkim'
mail_policyd_spf_user:   'policyd-spf'
mail_policyd_spf_group:  'policyd-spf'
mail_dovecot_user:       'dovecot'
mail_dovecot_group:      'dovecot'
mail_dovenull_user:      'dovenull'
mail_dovenull_group:     'dovenull'
mail_postgrey_user:      'postgrey'
mail_postgrey_group:     'postgrey'
mail_opendmarc_user:     'opendmarc'
mail_opendmarc_group:    'opendmarc'
mail_debian_spamd_user:  'debian-spamd'
mail_debian_spamd_group: 'debian-spamd'
mail_amavis_user:        'amavis'
mail_amavis_group:       'amavis'
mail_vmail_user:         'vmail'
mail_vmail_group:        'vmail'