Skip to content

Security: byjpr/mailserver

Security

docs/security.md

Security design

This document lists what the server protects against and how. Most defaults come from simple-nixos-mailserver (SNM); the rest is added in modules/.

Threats considered

  1. Someone spoofs your domain: they send mail that claims to come from you.
  2. Someone spoofs another domain to your users, e.g. phishing "from" a bank.
  3. The server is abused to send spam, through an open relay, a guessed or leaked password, or a compromised mail client.
  4. Passive or active interception of mail in transit.
  5. Compromise of the host through exposed services or weak administration.
  6. Leaked secrets from the git repository or the Nix store.

1. Spoofing your domains

Measure Where
SPF v=spf1 mx -all: only the MX host (and the relay, if configured) may send lib/default.nix
DKIM signature on all outgoing mail, 2048-bit RSA, one key per domain, rotatable selectors mail.nix, scripts/add-domain.sh
DMARC p=reject; sp=reject; adkim=s; aspf=s: receivers reject unauthenticated mail claiming to be from you, including from subdomains lib/default.nix
SPF record for the HELO name, so bounces can be authenticated too lib/default.nix
Port 25 refuses any envelope sender in your own domains. Your users send through port 465 with a password, so such mail is always forged mail.nix (local_sender_domains)
Authenticated users can only send as their own address or aliases, both in the envelope (reject_sender_login_mismatch) and in the From: header recipients see (an Rspamd prefilter rejects anything else), so one user can't impersonate another SNM, modules/rspamd/from-owner.lua
Aggregate DMARC reports are received at dmarc-reports@, so you see who tries to send as you settings.nix

2. Spoofed and unwanted inbound mail

Measure Where
Rspamd checks SPF, DKIM, DMARC and ARC on every message SNM
DMARC policies are enforced: a message that fails DMARC for a domain publishing p=reject is rejected, and one under p=quarantine is marked as spam. By default Rspamd only adds a score mail.nix (dmarc.conf)
Greylisting, DNS blocklists, Bayes, phishing and URL checks Rspamd defaults
A local validating DNS resolver (Knot Resolver), so DNSBL queries aren't blocked as they are from public resolvers, and DNSSEC is validated for DANE SNM
Strict SMTP: HELO required and must be a valid FQDN, sender and recipient domains must exist, pipelining before the greeting is rejected mail.nix
Unknown recipients are rejected during the SMTP dialogue (no backscatter) SNM (virtual_mailbox_maps)

3. Abuse of the server for sending spam

Measure Where
No open relay: only authenticated users (and localhost) can send to external domains SNM smtpd_relay_restrictions, mynetworks limited to localhost
Port 25 does not offer authentication at all, which removes it as a password-guessing target mail.nix
Authentication only over TLS, only on port 465 and IMAP on 993 SNM
Per-user outbound rate limit (default 200/hour, burst 50) mail.nix (ratelimit.conf)
Per-IP authentication rate limit in Postfix, plus fail2ban bans after 3 failed logins (exponentially longer bans for repeat offenders) mail.nix, hardening.nix
Passwords stored only as yescrypt hashes, at least 12 characters enforced by just passwd scripts/passwd.sh
Privacy-sensitive headers (client IP, user agent) stripped from submitted mail SNM

fail2ban deliberately doesn't watch port 25 protocol errors. Doing so eventually bans the outbound servers of Gmail or Outlook over a single misdirected message.

4. Transport security

Measure Where
TLS 1.2+ only, AEAD + ECDHE cipher suites for TLS 1.2, hybrid post-quantum key exchange (X25519MLKEM768) SNM
Implicit TLS for clients (993 and 465, RFC 8314); plaintext IMAP, POP3 and STARTTLS submission are disabled mail.nix
MTA-STS policy in enforce mode, so other servers must use verified TLS when delivering to you web.nix, lib/default.nix
TLS-RPT, so you get reports when someone fails to connect securely lib/default.nix
Outgoing mail validates DANE and MTA-STS of the receiver (postfix-tlspol) SNM
With a relay: certificate verification is mandatory (secure), so the relay password can never be sent in plaintext or to an impostor relay.nix
CAA record: only Let's Encrypt may issue certificates for the mail host lib/default.nix

5. Host hardening

Measure Where
Only ports 22, 25, 80, 443, 465 and 993 are open (nftables on the host; on DigitalOcean also its cloud firewall). SSH is only accepted from sshAllowedCidrs hardening.nix
SSH: keys only, no passwords, root only by key, modern algorithms only, no forwarding hardening.nix
The server's SSH host key is generated by just init and pinned in known_hosts; no trust on first use scripts/init.sh, scripts/install.sh
No passwords for any system user; immutable user database; sudo removed hardening.nix
Kernel: restricted pointers, dmesg, BPF, ptrace and kexec; no ICMP redirects or source routing; rp_filter; SYN cookies; rarely used network protocols blacklisted hardening.nix
nginx answers only for the mail host and the MTA-STS names; anything else gets a closed connection. No version banner web.nix
The Rspamd web UI is only on a local Unix socket SNM
Services run under systemd sandboxing where SNM/nixpkgs provide it SNM, nixpkgs
Weekly flake.lock update PR (GitHub Action), optional nightly auto-upgrade with reboot window .github/workflows/update.yml, hardening.nix
The server can't be destroyed by Terraform accidentally (prevent_destroy) infra/servers/*/main.tf
The provider's SMTP-blocking network firewall is turned off on netcup (its default "Mail block" policy), with the host firewall doing the filtering infra/servers/netcup/main.tf

6. Secrets

  • Secrets are encrypted with sops to two age keys: yours, and one derived from the server's SSH host key. The server decrypts them at boot into /run/secrets (tmpfs), readable only by the service that needs each one. Nothing secret enters the Nix store, which is world-readable.
  • The server's private SSH host key is stored in the repo encrypted to your key only, and is copied to the server once during just install.
  • Mailbox passwords never exist anywhere in plaintext except while you type them.
  • The public halves (DKIM public keys, host public key) are committed in plaintext on purpose: they are needed to generate DNS records and known_hosts.

If your age key leaks, generate a new one, update .sops.yaml, run sops updatekeys secrets/*, and rotate the mailbox passwords and DKIM keys.

Not covered

  • Encryption at rest. The server's disk isn't encrypted (an unattended server would need the key on the same machine).
  • Virus scanning (ClamAV) is off because of the 1 GB RAM budget; see the README.
  • DNSSEC for your own zones is up to your DNS host; turn it on in Cloudflare if you use it.
  • Two-factor authentication for IMAP/SMTP isn't something mail clients support. Use long random passwords (just passwd <address> --random).

There aren't any published security advisories