blog entry: draft my Postfix installation
This commit is contained in:
parent
cf84ba58d9
commit
dea118f1d3
|
@ -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?
|
||||
-->
|
Loading…
Reference in New Issue