diff --git a/content/blog/mail_server_alpine_postfix_dovecot_tutorial.md b/content/blog/mail_server_alpine_postfix_dovecot_tutorial.md index 7a1ab8a..d9376cc 100644 --- a/content/blog/mail_server_alpine_postfix_dovecot_tutorial.md +++ b/content/blog/mail_server_alpine_postfix_dovecot_tutorial.md @@ -39,8 +39,8 @@ server you yourself are using.[^server_trust] Running your own mail server also allows you to implement things your way, with the features you want. For instance, [you can run a sieve filter for encrypting all incoming mail with a user's public GPG -key](https://www.grepular.com/Automatically_Encrypting_all_Incoming_Email); for obvious reasons, users of externally -managed mail servers that implement sieve do not allow users to create their own executables for sieve filters. +key](https://www.grepular.com/Automatically_Encrypting_all_Incoming_Email); for obvious reasons, public (as in, open to +public sign-up) mail servers that implement sieve do not allow users to create their own executables for sieve filters. Hosting your own mail server is not something I would universally recommend to people. While I'm very much against "nothing to hide, nothing to fear", a combination of that factor alongside a low state threat model (i.e. there is @@ -108,7 +108,7 @@ in through a standard SMTP/IMAP/POP3 email client, read their emails, and send e modular, i.e. you can opt to have e.g. Pigeonhole but not Amavis. We will end up with a small-scale mail server running on Alpine Linux with one domain, and we will use Unix user -accounts as mail accounts. +accounts as mail accounts. We will only set up IMAP, not POP3. This tutorial was written for Alpine Linux 3.20.3, though will most likely work on other versions too. @@ -141,7 +141,7 @@ choice. An MX record denotes that your domain is used to send and receive email, and tells other MTAs the domain name of your mail server. We will use `mail.domain.com` for your MX record. For instance, my MX record looks like: -```bindzone +```dns revsuine.xyz. 14400 IN MX 0 mail.revsuine.xyz ``` @@ -153,19 +153,19 @@ same as the IP address of `domain.com`, or an A record if the IP address is not I use a CNAME record because the IP addresses of `mail.revsuine.xyz` and `revsuine.xyz` are the same, so my record is: -```bindzone +```dns mail.revsuine.xyz. 14400 IN CNAME revsuine.xyz ``` If you use an A record, your record may look something like -```bindzone +```dns mail.domain.com. 14400 IN A ip.address.here ``` If you use IPv6, you should also add an AAAA record, e.g.: -```bindzone +```dns mail.domain.com. 14400 IN AAAA ip:address:here:: ``` @@ -205,6 +205,57 @@ following TCP ports are open on your firewall: | 587 | Email message submission | | 993 | IMAPS (IMAP over TLS) | +## Obtain a TLS certificate + +To enable TLS encryption, you need a certificate. [Let's Encrypt](https://letsencrypt.org/) provides free TLS +certificates. To get a certificate from them, you can use certbot: + + # apk add certbot + +We will need a web server to use certbot. I'm going to use nginx for this guide, because nginx is what I use on my +server, but [the certbot website](https://certbot.eff.org/) has instructions for a variety of setups. If you don't +already have an nginx server, install nginx and set it up now. + +Install `certbot-nginx` with: + + # apk add certbot-nginx + +Add the following to your nginx config (for instance, inside `http {}` in `/etc/nginx/nginx.conf`, or in a dedicated +virtual host file `/etc/nginx/http.d/mail.domain.com.conf`): + +```nginx +server { + listen 80; + listen [::]:80; + server_name mail.domain.com; + + root /usr/share/nginx/html/; + + location ~ /.well-known/acme-challenge { + allow all; + } +} +``` + +Replace `mail.domain.com` with the FQDN of your mail server. + +The `root` can be set to any extant directory on your system that you're happy to publish to the web. You can just make +an empty directory at `/usr/share/nginx/html`, or make this the directory of your website, etc. + +Reload or restart nginx for the changes to take effect: + + # rc-service nginx reload + +Now run the following command to get your free TLS certificate: + + # certbot certonly -a nginx --staple-ocsp --email your@email.here -d mail.domain.com + +If you have several subdomains in your nginx config that you'd like covered by the same certificate, you can omit `-d +mail.domain.com` and get a certificate covering all the domains in your nginx config. On my server, I have one +certificate at `/etc/letsencrypt/live/revsuine.xyz/` covering my apex domain and all subdomains. If you go for a +certificate with only one domain name, e.g. for `mail.domain.com`, it will be at +`/etc/letsencrypt/live/mail.domain.com/`. + # Postfix Postfix is a [mail transport agent](https://en.wikipedia.org/wiki/Message_transfer_agent) (aka SMTP server). [In its @@ -257,6 +308,76 @@ one at `/etc/logrotate.d/postfix`: } ``` +Add the following TLS settings, replacing `your.domain.com` with your mail server's FQDN, [or otherwise where the TLS +certificate we generated would be](#obtain-a-tls-certificate): + +```conf +# Enable TLS encryption when Postfix receives incoming emails +smtpd_tls_cert_file = /etc/letsencrypt/live/your.domain.com/fullchain.pem +smtpd_tls_key_file = /etc/letsencrypt/live/your.domain.com/privkey.pem +smtpd_tls_security_level = may +smtpd_tls_loglevel = 1 +smtpd_tls_session_cache_database = lmdb:${data_directory}/smtpd_scache + +# Enable TLS encryption when Postfix sends outgoing emails +smtp_tls_security_level = may +smtp_tls_loglevel = 1 +smtp_tls_session_cache_database = lmdb:${data_directory}/smtp_scache + +# Enforce TLSv1.3 or TLSv1.2 +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 + +# only offer authentication after STARTTLS +smtpd_tls_auth_only = yes + +# disable SSL compression +tls_ssl_options = NO_COMPRESSION + +# Configure the allowed cipher list +smtpd_tls_mandatory_ciphers = high +smtp_tls_mandatory_ciphers = high +smtpd_tls_ciphers = high +smtpd_tls_mandatory_ciphers = high +tls_high_cipherlist = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +tls_preempt_cipherlist = yes +``` + +The allowed cipher list is from [Mailcow](https://docs.mailcow.email/manual-guides/Postfix/u_e-postfix-harden_ciphers/). + +If you're using this as a personal mail server, you may not want to have a mailbox size limit, so you can set: + +```conf +mailbox_size_limit = 0 +``` + +By default, `mailbox_size_limit` is `51200000`. This number is in bytes. You can similarly set a `message_size_limit`. + +Finally, here are some various hardening settings you can add to your `/etc/postfix/main.conf`: + +```conf +# connections rate limit: no of connections allowed per unit +# `postconf anvil_rate_time_unit` will give the time unit; by default it's +# 60 seconds, so 600/60=10 connections allowed per second +smtpd_client_connection_rate_limit = 600 +# messages rate limit, again over same time limit +smtpd_client_message_rate_limit = 60 +# VRFY command used to check if an email address exists +# not needed and can be used to find spam recipients +disable_vrfy_command = yes +# servers that don't use HELO or EHLO are either not properly configured +# or sending spam usually +smtpd_helo_required = yes +smtpd_delay_reject = yes +smtpd_helo_restrictions = + permit_mynetworks, + reject_invalid_helo_hostname, + reject_unknown_helo_hostname, + permit +``` + ## Send your first email Have the `postfix` service auto-start upon boot, and start it during this session: @@ -268,7 +389,7 @@ You can now send an email with the following command: $ echo "test email" | sendmail user@externaldomain.com -Send this email to your email account with an external server, e.g. a gmail account. Note that Protonmail has quite +Send this email to your email account with an external server, e.g. a Gmail account. Note that Protonmail has quite stringent spam filters and this likely would be rejected by Protonmail, i.e. not even reach your spam folder. ## Configure email aliases @@ -293,9 +414,119 @@ so ultimately `revsuine` will get `postmaster`'s mail. You can continue to populate the aliases file with whatever aliases you want. +## Enable Postfix submission and smtps service + +To send emails from email clients, you'll need to enable Postfix's submission service so that Postfix can receive +emails to send via SMTP. Edit `/etc/postfix/master.cf` and ensure that the following lines are present: + +```conf +submission inet n - n - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtp_sasl_type=dovecot + -o smtpd_sasl_path=private/auth + +smtps inet n - n - - smtpd + -o syslog_name=postfix/smtps + -o smtpd_tls_wrappermode=yes + -o smtpd_sasl_auth_enable=yes + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_sasl_type=dovecot + -o smtpd_sasl_path=private/auth +``` + +They may be commented out, or partially present without some options. + +Restart Postfix. + + # rc-service postfix restart + # Dovecot -[Dovecot](https://www.dovecot.org/) is a popular IMAP and POP3 server which we'll be using for our MDA. +[Dovecot](https://www.dovecot.org/) is a popular IMAP and POP3 server which we'll be using for our MDA. Let's install +it: + + # apk add dovecot + +Check the Dovecot version with: + + $ dovecot --version + +Now let's enable IMAP by editing `/etc/dovecot/dovecot.conf`. Find a `protocols = ` line, or add one, and set it to: + +```conf +protocols = imap +``` + +## Configure how to store emails + +You probably want to use the Maildir format for storing emails, where each user's mail is stored at `~/Maildir` (this +can be set to another location if desired). + +In `/etc/dovecot/conf.d/10-mail.conf`, set: + +```conf +mail_location = maildir:~/Maildir +mail_privileged_group = mail +``` + +`mail_privileged_group` tells us which group of Unix users can send mail; in this case, it's anyone in the `mail` +group. You can create the group with: + + # addgroup mail + # adduser postfix mail + # adduser dovecot mail + +We want to ensure that `postfix` and `dovecot` users have the right to access mail. + +To change the Maildir directory, e.g. to set it to `~/mail`, you would set the following: + +`/etc/dovecot/conf.d/10-mail.conf`: + +```conf +mail_location = maildir:~/mail +``` + +`/etc/postfix/main.cf`: + +```conf +home_mailbox = mail/ +``` + +## Get emails with LMTP + +LMTP is a protocol which can be used for Postfix to pass incoming +emails to Dovecot. To install it for Dovecot: + + # apk add dovecot-lmtpd + +Add `lmtp` to the supported protocols in `/etc/dovecot/dovecot.conf`: + +```conf +protocols = imap lmtp +``` + +Now change the LMTP service (or add if it isn't already there) in `/etc/dovecot/conf.d/10-master.conf` to: + +```conf +service lmtp { + unix_listener /var/spool/postfix/private/dovecot-lmtp { + mode = 0600 + user = postfix + group = postfix + } +``` + +Postfix needs to be configured to use this socket. Edit `/etc/postfix/main.cf` with the following lines: + +```conf +mailbox_transport = lmtp:unix:private/dovecot-lmtp +smtputf8_enable = no +``` @@ -312,7 +543,7 @@ You can continue to populate the aliases file with whatever aliases you want. For a dedicated server you rent, there are at least no concerns about a compromised host, but an attacker with physical access (in this case, the untrusted people you rent the dedicated server from) can attempt evil maid - attacks. You are hopefully able to implement things to detect this, though. + attacks. You are hopefully able to implement mechanisms to detect this, though. There are reasons you may want to go with a rented server instead of one you own, though. For instance, if you live in a jurisdiction known for terrible privacy laws such as a [5/14 eyes