summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hosts/surtr/email/default.nix225
-rw-r--r--hosts/surtr/postgresql.nix12
-rw-r--r--hosts/surtr/ruleset.nft10
3 files changed, 240 insertions, 7 deletions
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix
index 68690d55..5f4d9725 100644
--- a/hosts/surtr/email/default.nix
+++ b/hosts/surtr/email/default.nix
@@ -2,7 +2,14 @@
2 2
3with lib; 3with lib;
4 4
5{ 5let
6 compileSieve = name: text: pkgs.runCommand name {} ''
7 mkdir $out
8 cp ${pkgs.writeText name ''
9 ''} $out/${name}
10 ${pkgs.dovecot_pigeonhole}/bin/sievec $out/${name}
11 ''};
12in {
6 config = { 13 config = {
7 services.postfix = { 14 services.postfix = {
8 enable = true; 15 enable = true;
@@ -40,6 +47,8 @@ with lib;
40 #enable TLS logging to see the ciphers for outbound connections 47 #enable TLS logging to see the ciphers for outbound connections
41 smtp_tls_loglevel = "1"; 48 smtp_tls_loglevel = "1";
42 49
50 smtpd_tls_received_header = true;
51
43 smtpd_tls_ask_ccert = true; 52 smtpd_tls_ask_ccert = true;
44 smtpd_tls_CAfile = toString ./ca/ca.crt; 53 smtpd_tls_CAfile = toString ./ca/ca.crt;
45 54
@@ -88,8 +97,8 @@ with lib;
88 authorized_verp_clients = "$mynetworks"; 97 authorized_verp_clients = "$mynetworks";
89 98
90 milter_default_action = "accept"; 99 milter_default_action = "accept";
91 smtpd_milters = [config.services.opendkim.socket]; 100 smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"];
92 non_smtpd_milters = [config.services.opendkim.socket]; 101 non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"];
93 102
94 alias_maps = ""; 103 alias_maps = "";
95 104
@@ -113,6 +122,19 @@ with lib;
113 sender_canonical_classes = "envelope_sender"; 122 sender_canonical_classes = "envelope_sender";
114 recipient_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.reversePort}"; 123 recipient_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.reversePort}";
115 recipient_canonical_classes = ["envelope_recipient" "header_recipient"]; 124 recipient_canonical_classes = ["envelope_recipient" "header_recipient"];
125
126 virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" ''
127 dbname = emails
128 table = virtual_mailbox_domain
129 select_field = domain
130 where_field = domain
131 ''}'';
132 virtual_mailbox_maps = ''pgsql:${pkgs.writeText "virtual_mailbox_maps.cf" ''
133 dbname = emails
134 table = virtual_mailbox_mapping
135 select_field = mailbox
136 where_field = lookup
137 ''}'';
116 }; 138 };
117 masterConfig = { 139 masterConfig = {
118 smtps = { 140 smtps = {
@@ -155,6 +177,187 @@ with lib;
155 ''; 177 '';
156 }; 178 };
157 179
180 services.rspamd = {
181 enable = true;
182 workers = {
183 controller = {};
184 external = {
185 type = "rspamd_proxy";
186 bindSockets = [
187 { mode = "0660";
188 socket = "/run/rspamd/rspamd-milter.sock";
189 owner = config.services.rspamd.user;
190 group = config.services.rspamd.group;
191 }
192 ];
193 extraConfig = ''
194 milter = yes;
195
196 upstream "local" {
197 default = yes;
198 self_scan = yes;
199 }
200 '';
201 };
202 };
203 locals = {
204 "milter_headers.conf".text = ''
205 use = ["authentication-results", "x-spamd-result", "x-rspamd-queue-id", "x-rspamd-server", "x-spam-level", "x-spam-status"];
206 extended_headers_rcpt = [];
207 '';
208 "actions.conf".text = ''
209 reject = 15;
210 add_header = 10;
211 greylist = 5;
212 '';
213 "groups.conf".text = ''
214 symbols {
215 "BAYES_SPAM" {
216 weight = 2.0;
217 }
218 }
219 '';
220 "dmarc.conf".text = ''
221 reporting = true;
222 send_reports = true;
223 report_settings {
224 org_name = "Yggdrasil.li";
225 domain = "yggdrasil.li";
226 email = "postmaster@yggdrasil.li";
227 }
228 '';
229 "redis.conf".text = ''
230 servers = "${config.services.redis.servers.rspamd.unixSocket}";
231 '';
232 "dkim_signing.conf".text = "enabled = false;";
233 "neural.conf".text = "enabled = false;";
234 "classifier-bayes.conf".text = ''
235 enable = true;
236 expire = 8640000;
237 new_schema = true;
238 backend = "redis";
239 per_user = true;
240 min_learns = 0;
241
242 autolearn = [0, 10];
243
244 statfile {
245 symbol = "BAYES_HAM";
246 spam = false;
247 }
248 statfile {
249 symbol = "BAYES_SPAM";
250 spam = true;
251 }
252 '';
253 # "redirectors.inc".text = ''
254 # visit.creeper.host
255 # '';
256 };
257 };
258
259 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user ];
260
261 services.redis.servers.rspamd = {
262 enable = true;
263 vmOverCommit = true;
264 };
265
266 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ];
267
268 services.dovecot2 = {
269 enable = true;
270 enableImap = false;
271 sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem";
272 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem";
273 sslCACert = ./ca/ca.crt;
274 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8";
275 modules = with pkgs; [ dovecot_pigeonhole ];
276 protocols = [ "imaps" "lmtp" "sieve" ];
277 extraConfig = ''
278 mail_home = /var/lib/mail/%u
279
280 local_name imap.bouncy.email {
281 ssl_cert = <$/run/credentials/dovecot2.service/imap.bouncy.email.pem
282 ssl_key = <$/run/credentials/dovecot2.service/imap.bouncy.email.key.pem
283 }
284 local_name bouncy.email {
285 ssl_cert = <$/run/credentials/dovecot2.service/bouncy.email.pem
286 ssl_key = <$/run/credentials/dovecot2.service/bouncy.email.key.pem
287 }
288
289 ssl_require_crl = yes
290 ssl_verify_client_cert = yes
291 auth_ssl_username_from_cert = yes
292 auth_mechanisms = external
293
294 userdb sql {
295 args = ${pkgs.writeText "dovecot-sql.conf" ''
296 driver = pgsql
297 connect = host=localhost dbname=email
298 user_query = SELECT mailbox AS user, quota_rule FROM mailbox WHERE mailbox = '%u'
299 ''}
300 default_fields = uid=dovecot2 gid=dovecot2
301 }
302
303 mail_plugins = $mail_plugins quota
304 mailbox_list_index = yes
305 postmaster_address = postmaster@yggdrasil.li
306 recipient_delimiter = +
307
308 sieve_plugins = $sieve_plugins sieve_imapsieve
309
310 service lmtp {
311 vsz_limit = 1G
312
313 unix_listener /run/postfix/dovecot-lmtp {
314 mode = 0600
315 user = postfix
316 group = postfix
317 }
318 }
319
320 namespace inbox {
321 separator = /
322 inbox = yes
323 prefix =
324 }
325
326 plugin {
327 quota = maildir
328 quota_rule = *:storage=1GB
329 quota_rule2 = Trash:storage=+10%%
330 quota_status_overquota = "552 5.2.2 Mailbox is full"
331 quota_status_success = DUNNO
332 quota_status_nouser = DUNNO
333 quota_grace = 10%%
334 }
335
336 protocol imap {
337 mail_max_userip_connections = 50
338 mail_plugins = $mail_plugins imap_quota imap_sieve
339 }
340
341 service managesieve-login {
342 inet_listener sieve {
343 port = 4190
344 ssl = yes
345 }
346 }
347
348 plugin {
349 sieve_redirect_envelope_from = orig_recipient
350 sieve_before = ${compileSieve "tag-junk.sieve" ''
351 require ["imap4flags"];
352
353 if header :contains "X-Spam-Flag" "YES" {
354 addflag ["\\Junk"];
355 }
356 ''}
357 }
358 '';
359 };
360
158 security.dhparams = { 361 security.dhparams = {
159 params = { 362 params = {
160 "postfix-512".bits = 512; 363 "postfix-512".bits = 512;
@@ -166,6 +369,7 @@ with lib;
166 "bouncy.email" = {}; 369 "bouncy.email" = {};
167 "mailin.bouncy.email" = {}; 370 "mailin.bouncy.email" = {};
168 "mailsub.bouncy.email" = {}; 371 "mailsub.bouncy.email" = {};
372 "imap.bouncy.email" = {};
169 "surtr.yggdrasil.li" = {}; 373 "surtr.yggdrasil.li" = {};
170 }; 374 };
171 375
@@ -178,5 +382,20 @@ with lib;
178 "mailsub.bouncy.email.full.pem:${config.security.acme.certs."mailsub.bouncy.email".directory}/full.pem" 382 "mailsub.bouncy.email.full.pem:${config.security.acme.certs."mailsub.bouncy.email".directory}/full.pem"
179 ]; 383 ];
180 }; 384 };
385
386 systemd.services.dovecot2 = {
387 serviceConfig = {
388 LoadCredential = [
389 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem"
390 "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem"
391 "bouncy.email.key.pem:${config.security.acme.certs."bouncy.email".directory}/key.pem"
392 "bouncy.email.pem:${config.security.acme.certs."bouncy.email".directory}/fullchain.pem"
393 "imap.bouncy.email.key.pem:${config.security.acme.certs."imap.bouncy.email".directory}/key.pem"
394 "imap.bouncy.email.pem:${config.security.acme.certs."imap.bouncy.email".directory}/fullchain.pem"
395 ];
396
397 StateDirectory = "mail";
398 };
399 };
181 }; 400 };
182} 401}
diff --git a/hosts/surtr/postgresql.nix b/hosts/surtr/postgresql.nix
index 7b3b8c74..d8f66fcc 100644
--- a/hosts/surtr/postgresql.nix
+++ b/hosts/surtr/postgresql.nix
@@ -30,17 +30,23 @@ in {
30 BEGIN; 30 BEGIN;
31 SELECT _v.register_patch('000-base', null, null); 31 SELECT _v.register_patch('000-base', null, null);
32 32
33 CREATE TABLE virtual_mailbox_mapping ( 33 CREATE TABLE mailbox (
34 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
35 mailbox text NOT NULL CONSTRAINT mailbox_non_empty CHECK (mailbox <> '''),
36 quota_bytes bigint CONSTRAINT quota_bytes_positive CHECK (CASE WHEN quota_bytes IS NOT NULL THEN quota_bytes > 0 ELSE true),
37 quota_rule text GENERATED ALWAYS AS (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes) STORED
38 )
39 CREATE TABLE mailbox_mapping (
34 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), 40 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
35 local text CONSTRAINT local_non_empty CHECK (local IS DISTINCT FROM '''), 41 local text CONSTRAINT local_non_empty CHECK (local IS DISTINCT FROM '''),
36 domain text NOT NULL CONSTRAINT domain_non_empty CHECK (domain <> '''), 42 domain text NOT NULL CONSTRAINT domain_non_empty CHECK (domain <> '''),
37 mailbox text NOT NULL CONSTRAINT mailbox_non_empty CHECK (mailbox <> '''), 43 mailbox uuid REFERENCES virtual_mailbox(id)
38 CONSTRAINT local_domain_unique UNIQUE (local, domain) 44 CONSTRAINT local_domain_unique UNIQUE (local, domain)
39 ); 45 );
40 CREATE UNIQUE INDEX domain_unique ON virtual_mailbox_mapping (domain) WHERE local IS NULL; 46 CREATE UNIQUE INDEX domain_unique ON virtual_mailbox_mapping (domain) WHERE local IS NULL;
41 47
42 CREATE VIEW virtual_mailbox_domain (domain) AS SELECT DISTINCT domain FROM virtual_mailbox_mapping; 48 CREATE VIEW virtual_mailbox_domain (domain) AS SELECT DISTINCT domain FROM virtual_mailbox_mapping;
43 CREATE VIEW virtual_mailbox (mailbox) AS SELECT DISTINCT mailbox FROM virtual_mailbox_mapping; 49 CREATE VIEW virtual_mailbox_mapping (mailbox, lookup) AS SELECT mailbox.mailbox as mailbox, (CASE WHEN local IS NULL THEN ''' ELSE local) || '@' || domain AS lookup FROM mailbox_mapping INNER JOIN mailbox on mailbox.id = mailbox_mapping.mailbox;
44 COMMIT; 50 COMMIT;
45 ''} 51 ''}
46 ''; 52 '';
diff --git a/hosts/surtr/ruleset.nft b/hosts/surtr/ruleset.nft
index f5ad5769..b9f83487 100644
--- a/hosts/surtr/ruleset.nft
+++ b/hosts/surtr/ruleset.nft
@@ -80,6 +80,8 @@ table inet filter {
80 counter turn-rx {} 80 counter turn-rx {}
81 counter smtp-rx {} 81 counter smtp-rx {}
82 counter submissions-rx {} 82 counter submissions-rx {}
83 counter imap-rx {}
84 counter managesieve-rx {}
83 85
84 counter established-rx {} 86 counter established-rx {}
85 87
@@ -105,6 +107,8 @@ table inet filter {
105 counter turn-tx {} 107 counter turn-tx {}
106 counter smtp-tx {} 108 counter smtp-tx {}
107 counter submissions-tx {} 109 counter submissions-tx {}
110 counter imap-tx {}
111 counter managesieve-tx {}
108 112
109 counter tx {} 113 counter tx {}
110 114
@@ -170,8 +174,10 @@ table inet filter {
170 udp dport {3478, 5349} counter name stun-rx accept 174 udp dport {3478, 5349} counter name stun-rx accept
171 udp dport 49000-50000 counter name turn-rx accept 175 udp dport 49000-50000 counter name turn-rx accept
172 176
173 # tcp dport 25 counter name smtp-rx accept 177 tcp dport 25 counter name smtp-rx accept
174 tcp dport 465 counter name submissions-rx accept 178 tcp dport 465 counter name submissions-rx accept
179 tcp dport 993 counter name imaps-rx accept
180 tcp dport 4190 counter name managesieve-rx accept
175 181
176 ct state {established, related} counter name established-rx accept 182 ct state {established, related} counter name established-rx accept
177 183
@@ -214,6 +220,8 @@ table inet filter {
214 220
215 tcp sport 25 counter name smtp-tx accept 221 tcp sport 25 counter name smtp-tx accept
216 tcp sport 465 counter name submissions-tx accept 222 tcp sport 465 counter name submissions-tx accept
223 tcp sport 993 counter name imaps-tx accept
224 tcp sport 4190 counter name managesieve-tx accept
217 225
218 226
219 counter name tx 227 counter name tx