blog entry: draft my Postfix installation

This commit is contained in:
Colin 2022-04-06 06:56:50 +00:00
parent cf84ba58d9
commit dea118f1d3
1 changed files with 257 additions and 0 deletions

View File

@ -0,0 +1,257 @@
+++
title = "A Reasonably Secure Mailserver Installation"
description = ""
date = "2022-04-05"
extra.hidden = true
+++
i need software to receive emails, and possibly to send them too. i.e., a mailserver.
the mature mailserver implementations were all written in a time where security was
even worse than today. Postfix is among the better ones, but even it has 10 CVEs.
its intended operation -- where it writes to mailboxes owned by different users --
relies on elevated access control. although the risks are mitigated by its modular
design -- where only select portions of code get elevated permissions -- and the linux
capabilities system, i still would not feel comfortable running this without
isolating it from other applications operating on the same machine.
enter systemd-nspawn. nspawn is an extremely lightweight container. it's more of a transparent chroot:
package up the userspace of some linux distribution, place it in a directory, and then
nspawn uses the host kernel and performs the whole PID 1 boot sequence of that
chroot, virtualizing all the fs access and isolating the processes/etc. we'll use this
to create a container dedicated to postfix.
## Installation
start by creating the rootfs and launching it as a container. this assumes the host
is running Arch:
```sh
[root@host /]$ pacman -S arch-install-scripts
[root@host /]$ mkdir /opt/postfix
[root@host /]$ pacstrap -c /opt/postfix base postfix openbsd-netcat opendkim perl
[root@host /]$ systemd-nspawn -D /opt/postfix
># passwd # choose a [temporary] password you can remember for the rest of setup
># exit
```
if you're ssh'd into the host, you need to [relax some security settings](https://wiki.archlinux.org/title/Systemd-nspawn#Root_login_fails)
in the container before it'll let you login:
```sh
[root@host /opt/postfix]$ mv etc/securetty etc/securetty.OLD
[root@host /opt/postfix]$ mv usr/share/factory/etc/securetty \
usr/share/factory/etc/securetty.OLD
# then comment out the line containing securetty in usr/lib/tmpfiles.d/arch.conf
```
configure `myhostname` and `mydomain` in `/opt/postfix/etc/postfix/main.cf` (it's fine for these to be the same: i use `uninsane.org` for both)
using the `--network-veth` flag, systemd will create a NAT'd network and expose the downstream to the container.
we can then forward ports across the NAT just like you would forward ports from your router to your PC/server (port 25 here is the SMTP port):
```sh
[root /]$ systemd-nspawn -b --network-veth -p 25:25 -D /opt/postfix
postfix login: root
Password: <enter it>
[root@postfix ~]# systemctl enable systemd-resolved && systemctl start systemd-resolved
[root@postfix ~]# systemctl enable postfix && systemctl start postfix
# then create the db which postfix uses to map email address to linux user accounts:
[root@postfix ~]# newaliases
```
for the record, `/etc/postfix/aliases` contains the mappings consumed by `newaliases`. the defaults work for us now but you'll
want to tweak them later.
like HTTP, the SMTP grammar is human friendly. we can [verify our setup with netcat](https://wiki.archlinux.org/title/Virtual_user_mail_system_with_Postfix,_Dovecot_and_Roundcube#Testing).
you can do this from within the container (substitute `localhost` for `<container>`), or from another box on your LAN (substitute the host's IP/name for `<container>`).
you probably don't want to expose this to the WAN yet:
```sh
$ nc <container> 25
helo uninsane.org
mail from:<test@uninsane.org>
rcpt to:<root@uninsane.org>
data
this is a test.
.
quit
```
mail should show up in the container at `var/spool/mail/root`.
if this is intended as a single-user mailserver, you might want a catch-all mail rule.
append `@uninsane.org root` to the bottom of `etc/postfix/virtual`,
add `virtual_alias_maps = hash:/etc/postfix/virtual` to `etc/postfix/main.cf`
and then (in the container) run `postmap /etc/postfix/virtual` and restart the service.
try the `nc` command from above again, but use `rcpt to:<anything@uninsane.org` and
the mail should be appended to that same `/var/spool/mail/root` file.
## Non-Root User
we'd prefer to be able to read mail _without_ being root. so create a user dedicated to holding the mailboxes:
```sh
[root@postfix /]# useradd --create-home --user-group vmail
```
change `mail_owner` in etc/postfix/main.cf to be `vmail`, and restart the service.
in `etc/postfix/aliases` change `root: you` to `root: vmail`.
then change `etc/postfix/virtual` to map to `vmail` by appending this to the bottom:
```
@uninsane.org vmail
```
update the database mappings and then restart the services:
```sh
[root@postfix /]# newaliases
[root@postfix /]# postmap /etc/postfix/virtual
[root@postfix /]# postfix set-permissions
```
run that `nc` command again: this time mail should show up in `/var/spool/mail/vmail`,
and that file should be owned by the `vmail` user instead of `root`.
now we can work on the WAN side of things. to prevent spoofing & improve the likelihood that your
messages will be accepted by other servers, you'll want to add some DNS records to your zone file:
- a SPF DNS record (instructs recipients to enforce that your message originates from a specific IP)
- a DKIM DNS record (signs the message content with a key owned by your mailserver)
- a DMARC DNS record (allows you to receive reports from recipient mailservers)
and of course you'll need a MX record so others know where to send mail.
## DKIM and DNS
we installed opendkim during the earlier `pacstrap` invocation: now we'll configure it to sign
outgoing messages:
```sh
[root@host /opt/postfix]$ cp usr/share/doc/opendkim/opendkim.conf.sample \
etc/opendkim/opendkim.conf
# update the `Domain` field
# point the `KeyFile` to /home/vmail/dkim/mx1.private (we'll generate that in a second)
# set `UserID` to `vmail`
# make sure `Socket` points to `inet:8891@localhost`
# and consider changing Canonicalization from simple/simple to relaxed/simple
```
then append this to `etc/postfix/main.cf`:
```sh
# For use by dkim milter
smtpd_milters = inet:localhost:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
```
generate the keys (run this as the `vmail` user):
```sh
[vmail@postfix /home/vmail]$ mkdir dkim && cd dkim
[vmail@postfix /home/vmail/dkim]$ opendkim-genkey -r -s mx1 -d uninsane.org
```
start the service:
```
[root@postfix /]# systemctl start opendkim && systemctl enable opendkim
```
add the `mx1._domainkey` TXT record (documented in /home/vmail/dkim/mx1.txt) into your zone file.
then run the `nc` example again. you should get mail that has an `Authentication-Results` header -- which fails,
since we didn't sign our message.
using the postfix `sendmail` command we should be able to send something with a valid signature:
```sh
[root@postfix /]# sendmail test@uninsane.org
this message should be signed
.
[root@postfix /]# cat /var/mail/vmail
[...]
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=uninsane.org; s=mx1;
t=[...]; bh=[...]
h=Date:From;
b=[...]
Message-Id: <YYYYMMDDTTTTTT.NNNNNNNNNNN@uninsane.org>
Date: [...]
From: root@uninsane.org
this message should be signed
```
then add a SPF DNS record and a DMARC record to receive delivery reports.
if you're running a large mail server it would be good to install `opendmarc` to send delivery reports to *other* servers (like me!), but i'll skip that here.
throw in the MX record, and your zone file should look like this:
```zone
@ MX 10 uninsane.org.
; Sender Policy Framework:
; +mx => mail passes if it originated from the MX
; +a => mail passes if it originated from the A address of this domain
; +ip4:.. => mail passes if it originated from this IP
; -all => mail fails if none of these conditions were met
@ TXT "v=spf1 ip4:203.0.113.1 a mx -all"
; DKIM public key:
mx1._domainkey TXT "v=DKIM1; k=rsa; s=email; p=<big long string>"
; DMARC fields <https://datatracker.ietf.org/doc/html/rfc7489>:
; p=none|quarantine|reject: what to do with failures
; sp = p but for subdomains
; rua = where to send aggregrate reports
; ruf = where to send individual failure reports
; fo=0|1|d|s controls WHEN to send failure reports
; (1=on bad alignment; d=on DKIM failure; s=on SPF failure);
; Additionally:
; adkim=r|s (is DKIM relaxed [default] or strict)
; aspf=r|s (is SPF relaxed [default] or strict)
; pct = sampling ratio for punishing failures (default 100 for 100%)
; rf = report format
; ri = report interval
_dmarc TXT ("v=DMARC1;p=quarantine;sp=reject;fo=1:d:s;"
"rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org")
```
## Validation
validate your DMARC record (and DKIM, SPF if you want): <https://dmarcian.com/dmarc-inspector/>.
try sending/receiving mail: <https://www.appmaildev.com/en/dkim>.
if these fail, check `journalctl -u postfix`. if there's _nothing_ showing traffic, it may be that your ISP blocks port 25.
you can check for that with `nc -vz gmail.com 25` (will exit 0 if the port is open, hang if the port is blocked).
in my case, Centurylink blocks both port 25 outbound _and_ inbound, meaning that i can't even use this setup to _receive_ mail.
for this case, i'll explore running postfix on a non-standard port and using a mail forwarder or transparent proxy in a subsequent blog post.
<!--
Notes:
pass: (itsallinyour...)
https://wiki.archlinux.org/title/OpenDMARC
gmail.com has port 25 open, but not 465 nor 587. so it probably only does plain SMTP with STARTTLS
*inbound* port 25 is blocked, so i can't *receive* email
VPN?
https://www.ovpn.com/en/features/public-ipv4
IPv4 addr -- with PTR -- for $3/mo + $5-7/mo VPN service.
explicitly mentions email support.
allows BTC payments
https://www.perfect-privacy.com
*maybe* supports explicitly port-forwarding. $10-12/mo.
Vultr reverse proxy
MX forwarder?
https://www.dnsexit.com/Direct.sv?cmd=mailRedirect
$25/yr; no account limit. requires port 26 or 940
mailgun
free for small volume?
Mandrill
SendGrid
SMTP2Go
Amazon SES
https://ghettosmtp.com/
receive only, budgeted to 2000 mails/month
no outbout SMTP, except FAQ #13 mentions a secret test/beta
manual activation, by emailing them & asking access.
CenturyLink might allow outbound email via their OWN forwarder?
-->