summaryrefslogtreecommitdiff
path: root/content/tech/nixos-mailserver.md
blob: 8b5e19ba7243cc9e999f554b9c84bf7891ad0284 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
---
date: 2016-06-11
title: NixOS mail server
draft: true
---

<!--more-->
This is my working, but in progress [NixOS]({{< ref "nixos-intro.md" >}}) mail server configuration. These are the relevant configuration parts, they are *not* tested and **not guaranteed to be secure, deploy at your own risk**. The configuration is based on [this](https://thomas-leister.de/allgemein/sicherer-mailserver-dovecot-postfix-virtuellen-benutzern-mysql-ubuntu-server-xenial/) blog post (German).

For an easier overview, I created a seperate `mail.nix` that is included in the main `configuration.nix`. The firewall needs to be open for ports 993 (imps), 25 (smtp) and 587 (smtps).

```nix
imports =
  [
    ./mail.nix
  ];

networking.firewall.allowedTCPPorts = [ 993 25 587 ];
```

The server is built with postfix. Postfix transports the mail via smtp(s). Dovecot is the interface between user and provides pop3(s) and/or imap(s). Opendkim signs outgoing mail so that it is less likely to be marked as spam by other providers.

DNS records must be and a PTR record can be set up. This is not covered here.

It is advisable to use SSL certificates. NixOS has a module that gets Let's Encrypt certificates:

```nix
security.acme.certs = {
  "yourdomain.tld" = {
    webroot = "/var/www/challenges";
    extraDomains = { "mail.yourdomain.tld" = null; };
    email = "mail@provider.net";
    postRun = "systemctl reload nginx.service postfix.service dovecot2.service";
  };
};
```

A web server is needed for the certification process.

```nix
services.nginx = {
  enable = true;
  httpConfig = ''
    include ${pkgs.nginx}/conf/mime.types;

    server {
      listen 80;
      listen [::]:80;
      server_name mail.yourdomain.tld;
      location /.well-known/acme-challenge {
        root /var/www/challenges/;
      }
    }
  '';
};
```

After retrieving certificates once: `systemctl start acme-yourdomain.tld`, they will be regenerated regularly and placed in `/var/lib/acme/yourdomain.tld/` by default. Now comes the main configuration. The user `vmail` is the owner of the mailing system.

```nix
users.users.vmail = {
  home = "/var/vmail";
  createHome = true;
  group = "vmail";
};
users.groups = { vmail = {}; };
```

Dovecot requires an external configuration file. I put it in `/etc/nixos/mail/dovecot.conf`.

```nix
services.dovecot2 = {
  enable = true;
  configFile = "/etc/nixos/mail/dovecot.conf";
  sslCACert = "/var/lib/acme/yourdomain.tld/fullchain.pem";
  sslServerCert = "/var/lib/acme/yourdomain.tld/fullchain.pem";
  sslServerKey = "/var/lib/acme/yourdomain.tld/key.pem";
};
```

This is the dovecot config. It exposes imap, lmtp and auth ports. imap is for the user. lmtp and auth are for exchanging data between dovecot and postfix. Postfix will authenticate via dovecot. `passdb` and `userdb` set to `pam` and `passwd` will allow every local unix user to authenticate with their username and password. Each user has a `~/Maildir` with their mail, each mailbox contains the folders "Spam", "Trash", "Drafts" and "Sent".

```perl
default_internal_user = dovecot2

protocols = imap lmtp

ssl = required
ssl_cert = </var/lib/acme/yourdomain.tld/fullchain.pem
ssl_key = </var/lib/acme/yourdomain.tld/key.pem
ssl_dh_parameters_length = 2048
ssl_protocols = !SSLv2 !SSLv3
ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
ssl_prefer_server_ciphers = yes

service imap-login {
    inet_listener imap {
        port = 143
    }
}

service lmtp {
    inet_listener {
      port = 8458
    }
}


service auth {
    inet_listener {
        port = 2847
    }
}

protocol imap {
    mail_plugins = $mail_plugins
    mail_max_userip_connections = 20
    imap_idle_notify_interval = 29 mins
}

protocol lmtp {
    postmaster_address = postmaster@yourdomain.tld
    mail_plugins = $mail_plugins
}

disable_plaintext_auth = yes
auth_mechanisms = plain login

auth_username_format = %Ln
passdb {
    driver = pam
    args = dovecot2
}
userdb {
    driver = passwd
    args = blocking=no
}

mail_uid = vmail
mail_gid = vmail
mail_privileged_group = vmail

mail_home = /var/vmail/mailboxes/%d/%n
mail_location = maildir:~/Maildir

namespace inbox {
    inbox = yes

    mailbox Spam {
        auto = subscribe
        special_use = \Junk
    }

    mailbox Trash {
        auto = subscribe
        special_use = \Trash
    }

    mailbox Drafts {
        auto = subscribe
        special_use = \Drafts
    }

    mailbox Sent {
        auto = subscribe
        special_use = \Sent
    }
}
```

Now comes postfix. Postfix is configured to authenticate via dovecot. Postscreen and dnsblog filter some spam mails.

```nix
  services.postfix = {
    enable = true;
    user = "vmail";
    group = "vmail";
    domain = "mail.yourdomain.tld";
    hostname = "mail.yourdomain.tld";
    sslCACert = "/var/lib/acme/yourdomain.tld/fullchain.pem";
    sslCert = "/var/lib/acme/yourdomain.tld/fullchain.pem";
    sslKey = "/var/lib/acme/yourdomain.tld/key.pem";
    recipientDelimiter = "+";
    destination = [ "yourdomain.tld" "mail.yourdomain.tld" ];
    virtual = ''
onealias@yourdomain.tld youruser
anotheralias@yourdomain.tld youruser
    '';
    extraMasterConf = ''
smtp	inet	n	-	n	-	1	postscreen
	-o smtpd_sasl_auth_enable=no
smtpd	pass	-	-	n	-	-	smtpd
	-o smtpd_sasl_auth_enable=no
dnsblog	unix	-	-	n	-	0	dnsblog
tlsproxy	unix	-	-	n	-	0	tlsproxy
submission	inet	n	-	n	-	-	smtpd
    -o syslog_name=postfix/submission
    -o smtpd_tls_security_level=encrypt
    -o smtpd_sasl_auth_enable=yes
    -o smtpd_sasl_type=dovecot
    -o smtpd_sasl_path=inet:127.0.0.1:2847
    -o smtpd_sasl_security_options=noanonymous
    -o smtpd_relay_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_mynetworks,permit_sasl_authenticated,reject
    -o smtpd_sender_restrictions=permit_mynetworks,reject_non_fqdn_sender,permit_sasl_authenticated,reject
    -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
    -o smtpd_helo_required=no
    -o smtpd_helo_restrictions=
    -o milter_macro_daemon_name=ORIGINATING
    -o cleanup_service_name=submission-header-cleanup
submission-header-cleanup	unix	n	-	n	-	0	cleanup
    -o header_checks=pcre:/etc/nixos/mail/anonymize_headers.pcre
'';
    extraConfig = ''
maximal_queue_lifetime = 1h
bounce_queue_lifetime = 1h
maximal_backoff_time = 15m
minimal_backoff_time = 5m
queue_run_delay = 5m

tls_ssl_options = NO_COMPRESSION
tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
smtp_tls_security_level = dane
smtp_dns_support_level = dnssec
smtp_tls_session_cache_database = btree:''${data_directory}/smtp_scache
smtp_tls_protocols = !SSLv2, !SSLv3
smtp_tls_ciphers = high
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3
smtpd_tls_ciphers = high
smtpd_tls_eecdh_grade = strong
smtpd_tls_dh1024_param_file=/var/lib/acme/yourdomain.tld/dhparams.pem
smtpd_tls_session_cache_database = btree:''${data_directory}/smtpd_scache

smtpd_relay_restrictions =
                                reject_non_fqdn_recipient
                                reject_unknown_recipient_domain
                                permit_mynetworks
                                reject_unauth_destination
smtpd_client_restrictions =     permit_mynetworks
                                reject_unknown_client_hostname
smtpd_helo_required = yes
smtpd_helo_restrictions =   permit_mynetworks
                            reject_invalid_helo_hostname
                            reject_non_fqdn_helo_hostname
                            reject_unknown_helo_hostname
smtpd_data_restrictions = reject_unauth_pipelining


mailbox_transport = lmtp:inet:127.0.0.1:8458

milter_default_action = accept
milter_protocol = 2
smtpd_milters = unix:/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock

# Postscreen
postscreen_access_list =        permit_mynetworks
postscreen_blacklist_action = drop
postscreen_greet_action = drop

# DNS blocklists
postscreen_dnsbl_threshold = 2
postscreen_dnsbl_sites = dnsbl.sorbs.net*1, bl.spamcop.net*1, ix.dnsbl.manitu.net*2, zen.spamhaus.org*2
postscreen_dnsbl_action = drop

# turn off biff notifications for performance
biff = no

append_dot_mydomain = no
    '';
};
```

`/etc/nixos/mail/anonymize_headers.pcre` replaces sensitive header data.

```
/^\s*(Received: from)[^\n]*(.*)/ REPLACE $1 [127.0.0.1] (localhost [127.0.0.1])$2
/^\s*User-Agent/        IGNORE
/^\s*X-Enigmail/        IGNORE
/^\s*X-Mailer/          IGNORE
/^\s*X-Originating-IP/  IGNORE
```

Finally, the opendkim configuration. Generate a key once with `opendkim-genkey -r -d yourdomain.tld`. Add the public key as DNS record.

```nix
services.opendkim = {
    enable = true;
    domains = "yourdomain.tld,mail.yourdomain.tld";
    keyFile = "/etc/nixos/mail/key.private";
    selector = "key";
    socket = "local:/run/opendkim/opendkim.sock";
    user = "vmail";
    group = "vmail";
};
```