diff options
Diffstat (limited to 'hosts/surtr/email/default.nix')
-rw-r--r-- | hosts/surtr/email/default.nix | 145 |
1 files changed, 111 insertions, 34 deletions
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix index 4196a8bc..c993bb18 100644 --- a/hosts/surtr/email/default.nix +++ b/hosts/surtr/email/default.nix | |||
@@ -1,4 +1,4 @@ | |||
1 | { config, pkgs, lib, flakeInputs, ... }: | 1 | { config, pkgs, lib, flake, flakeInputs, ... }: |
2 | 2 | ||
3 | with lib; | 3 | with lib; |
4 | 4 | ||
@@ -15,7 +15,7 @@ let | |||
15 | 15 | ||
16 | for file in $out/pipe/bin/*; do | 16 | for file in $out/pipe/bin/*; do |
17 | wrapProgram $file \ | 17 | wrapProgram $file \ |
18 | --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" | 18 | --set PATH "${makeBinPath (with pkgs; [coreutils rspamd])}" |
19 | done | 19 | done |
20 | ''; | 20 | ''; |
21 | }; | 21 | }; |
@@ -33,12 +33,28 @@ let | |||
33 | }); | 33 | }); |
34 | }); | 34 | }); |
35 | }; | 35 | }; |
36 | internal-policy-server = | ||
37 | let | ||
38 | workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./internal-policy-server; }; | ||
39 | pythonSet = flake.lib.pythonSet { | ||
40 | inherit pkgs; | ||
41 | python = pkgs.python312; | ||
42 | overlay = workspace.mkPyprojectOverlay { | ||
43 | sourcePreference = "wheel"; | ||
44 | }; | ||
45 | }; | ||
46 | virtualEnv = pythonSet.mkVirtualEnv "internal-policy-server-env" workspace.deps.default; | ||
47 | in virtualEnv.overrideAttrs (oldAttrs: { | ||
48 | meta = (oldAttrs.meta or {}) // { | ||
49 | mainProgram = "internal-policy-server"; | ||
50 | }; | ||
51 | }); | ||
36 | 52 | ||
37 | nftables-nologin-script = pkgs.writeScript "nftables-mail-nologin" '' | 53 | nftables-nologin-script = pkgs.resholve.writeScript "nftables-mail-nologin" { |
38 | #!${pkgs.zsh}/bin/zsh | 54 | inputs = with pkgs; [inetutils nftables gnugrep findutils]; |
39 | 55 | interpreter = lib.getExe pkgs.zsh; | |
56 | } '' | ||
40 | set -e | 57 | set -e |
41 | export PATH="${lib.makeBinPath (with pkgs; [inetutils nftables])}:$PATH" | ||
42 | 58 | ||
43 | typeset -a as_sets mnt_bys route route6 | 59 | typeset -a as_sets mnt_bys route route6 |
44 | as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) | 60 | as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) |
@@ -51,7 +67,7 @@ let | |||
51 | elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then | 67 | elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then |
52 | route6+=($match[1]) | 68 | route6+=($match[1]) |
53 | fi | 69 | fi |
54 | done < <(whois -h whois.radb.net "!i''${as_set},1" | egrep -o 'AS[0-9]+' | xargs -- whois -h whois.radb.net -- -i origin) | 70 | done < <(whois -h whois.radb.net "!i''${as_set},1" | grep -Eo 'AS[0-9]+' | xargs whois -h whois.radb.net -- -i origin) |
55 | done | 71 | done |
56 | for mnt_by in $mnt_bys; do | 72 | for mnt_by in $mnt_bys; do |
57 | while IFS=$'\n' read line; do | 73 | while IFS=$'\n' read line; do |
@@ -190,16 +206,19 @@ in { | |||
190 | "reject_unauth_destination" | 206 | "reject_unauth_destination" |
191 | "reject_unknown_recipient_domain" | 207 | "reject_unknown_recipient_domain" |
192 | "reject_unverified_recipient" | 208 | "reject_unverified_recipient" |
209 | "check_policy_service unix:/run/postfix-internal-policy.sock" | ||
193 | ]; | 210 | ]; |
194 | unverified_recipient_reject_code = "550"; | 211 | unverified_recipient_reject_code = "550"; |
195 | unverified_recipient_reject_reason = "Recipient address lookup failed"; | 212 | unverified_recipient_reject_reason = "Recipient address lookup failed"; |
196 | address_verify_map = "internal:address_verify_map"; | 213 | address_verify_map = "internal:address_verify_map"; |
197 | address_verify_positive_expire_time = "1h"; | 214 | address_verify_positive_expire_time = "1h"; |
198 | address_verify_positive_refresh_time = "15m"; | 215 | address_verify_positive_refresh_time = "15m"; |
199 | address_verify_negative_expire_time = "15s"; | 216 | address_verify_negative_expire_time = "5m"; |
200 | address_verify_negative_refresh_time = "5s"; | 217 | address_verify_negative_refresh_time = "1m"; |
201 | address_verify_cache_cleanup_interval = "5s"; | 218 | address_verify_cache_cleanup_interval = "12h"; |
219 | address_verify_poll_count = "\${stress?15}\${stress:30}"; | ||
202 | address_verify_poll_delay = "1s"; | 220 | address_verify_poll_delay = "1s"; |
221 | address_verify_sender_ttl = "30045s"; | ||
203 | 222 | ||
204 | smtpd_relay_restrictions = [ | 223 | smtpd_relay_restrictions = [ |
205 | "check_ccert_access ${relay_ccert}" | 224 | "check_ccert_access ${relay_ccert}" |
@@ -213,7 +232,7 @@ in { | |||
213 | smtpd_client_event_limit_exceptions = ""; | 232 | smtpd_client_event_limit_exceptions = ""; |
214 | 233 | ||
215 | milter_default_action = "accept"; | 234 | milter_default_action = "accept"; |
216 | smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; | 235 | smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock" "local:/run/postsrsd/postsrsd-milter.sock"]; |
217 | non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; | 236 | non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; |
218 | 237 | ||
219 | alias_maps = ""; | 238 | alias_maps = ""; |
@@ -235,11 +254,6 @@ in { | |||
235 | ::/0 silent-discard, dsn | 254 | ::/0 silent-discard, dsn |
236 | ''}"; | 255 | ''}"; |
237 | 256 | ||
238 | sender_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.forwardPort}"; | ||
239 | sender_canonical_classes = "envelope_sender"; | ||
240 | recipient_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.reversePort}"; | ||
241 | recipient_canonical_classes = ["envelope_recipient" "header_recipient"]; | ||
242 | |||
243 | virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' | 257 | virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' |
244 | hosts = postgresql:///email | 258 | hosts = postgresql:///email |
245 | dbname = email | 259 | dbname = email |
@@ -254,11 +268,24 @@ in { | |||
254 | virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; | 268 | virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; |
255 | smtputf8_enable = false; | 269 | smtputf8_enable = false; |
256 | 270 | ||
257 | authorized_submit_users = "inline:{ root= postfwd= }"; | 271 | authorized_submit_users = "inline:{ root= postfwd= dovecot2= }"; |
272 | authorized_flush_users = "inline:{ root= }"; | ||
273 | authorized_mailq_users = "inline:{ root= }"; | ||
258 | 274 | ||
259 | postscreen_access_list = ""; | 275 | postscreen_access_list = ""; |
260 | postscreen_denylist_action = "drop"; | 276 | postscreen_denylist_action = "drop"; |
261 | postscreen_greet_action = "enforce"; | 277 | postscreen_greet_action = "enforce"; |
278 | |||
279 | sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" '' | ||
280 | hosts = postgresql:///email | ||
281 | dbname = email | ||
282 | query = SELECT value FROM sender_bcc_maps WHERE key = '%s' | ||
283 | ''}''; | ||
284 | recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" '' | ||
285 | hosts = postgresql:///email | ||
286 | dbname = email | ||
287 | query = SELECT value FROM recipient_bcc_maps WHERE key = '%s' | ||
288 | ''}''; | ||
262 | }; | 289 | }; |
263 | masterConfig = { | 290 | masterConfig = { |
264 | "465" = { | 291 | "465" = { |
@@ -283,7 +310,7 @@ in { | |||
283 | hosts = postgresql:///email | 310 | hosts = postgresql:///email |
284 | dbname = email | 311 | dbname = email |
285 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) | 312 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) |
286 | ''},permit_tls_all_clientcerts,reject}'' | 313 | ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}'' |
287 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" | 314 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" |
288 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | 315 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" |
289 | "-o" "unverified_sender_reject_code=550" | 316 | "-o" "unverified_sender_reject_code=550" |
@@ -313,7 +340,7 @@ in { | |||
313 | hosts = postgresql:///email | 340 | hosts = postgresql:///email |
314 | dbname = email | 341 | dbname = email |
315 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) | 342 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) |
316 | ''},permit_sasl_authenticated,reject}'' | 343 | ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}'' |
317 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" | 344 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" |
318 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | 345 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" |
319 | "-o" "unverified_sender_reject_code=550" | 346 | "-o" "unverified_sender_reject_code=550" |
@@ -364,17 +391,19 @@ in { | |||
364 | 391 | ||
365 | services.postsrsd = { | 392 | services.postsrsd = { |
366 | enable = true; | 393 | enable = true; |
367 | domain = "surtr.yggdrasil.li"; | 394 | domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; |
368 | separator = "+"; | 395 | separator = "+"; |
369 | excludeDomains = [ "surtr.yggdrasil.li" | 396 | extraConfig = '' |
370 | ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; | 397 | socketmap = unix:/run/postsrsd/postsrsd-socketmap.sock |
398 | milter = unix:/run/postsrsd/postsrsd-milter.sock | ||
399 | ''; | ||
371 | }; | 400 | }; |
372 | 401 | ||
373 | services.opendkim = { | 402 | services.opendkim = { |
374 | enable = true; | 403 | enable = true; |
375 | user = "postfix"; group = "postfix"; | 404 | user = "postfix"; group = "postfix"; |
376 | socket = "local:/run/opendkim/opendkim.sock"; | 405 | socket = "local:/run/opendkim/opendkim.sock"; |
377 | domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; | 406 | domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}''; |
378 | selector = "surtr"; | 407 | selector = "surtr"; |
379 | configFile = builtins.toFile "opendkim.conf" '' | 408 | configFile = builtins.toFile "opendkim.conf" '' |
380 | Syslog true | 409 | Syslog true |
@@ -484,6 +513,7 @@ in { | |||
484 | 513 | ||
485 | users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; | 514 | users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; |
486 | 515 | ||
516 | environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ]; | ||
487 | services.dovecot2 = { | 517 | services.dovecot2 = { |
488 | enable = true; | 518 | enable = true; |
489 | enablePAM = false; | 519 | enablePAM = false; |
@@ -491,7 +521,6 @@ in { | |||
491 | sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; | 521 | sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; |
492 | sslCACert = toString ./ca/ca.crt; | 522 | sslCACert = toString ./ca/ca.crt; |
493 | mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; | 523 | mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; |
494 | modules = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ]; | ||
495 | mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; | 524 | mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; |
496 | protocols = [ "lmtp" "sieve" ]; | 525 | protocols = [ "lmtp" "sieve" ]; |
497 | sieve = { | 526 | sieve = { |
@@ -673,7 +702,7 @@ in { | |||
673 | plugin { | 702 | plugin { |
674 | plugin = fts fts_xapian | 703 | plugin = fts fts_xapian |
675 | fts = xapian | 704 | fts = xapian |
676 | fts_xapian = partial=2 full=20 attachments=1 verbose=1 | 705 | fts_xapian = partial=3 full=20 attachments=1 verbose=1 |
677 | 706 | ||
678 | fts_autoindex = yes | 707 | fts_autoindex = yes |
679 | 708 | ||
@@ -693,7 +722,7 @@ in { | |||
693 | startAt = "*-*-* 22:00:00 Europe/Berlin"; | 722 | startAt = "*-*-* 22:00:00 Europe/Berlin"; |
694 | serviceConfig = { | 723 | serviceConfig = { |
695 | Type = "oneshot"; | 724 | Type = "oneshot"; |
696 | ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; | 725 | ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A"; |
697 | PrivateDevices = true; | 726 | PrivateDevices = true; |
698 | PrivateNetwork = true; | 727 | PrivateNetwork = true; |
699 | ProtectKernelTunables = true; | 728 | ProtectKernelTunables = true; |
@@ -778,7 +807,7 @@ in { | |||
778 | systemd.services.dovecot2 = { | 807 | systemd.services.dovecot2 = { |
779 | preStart = '' | 808 | preStart = '' |
780 | for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do | 809 | for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do |
781 | ${pkgs.dovecot_pigeonhole}/bin/sievec $f | 810 | ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f |
782 | done | 811 | done |
783 | ''; | 812 | ''; |
784 | 813 | ||
@@ -845,15 +874,16 @@ in { | |||
845 | charset utf-8; | 874 | charset utf-8; |
846 | source_charset utf-8; | 875 | source_charset utf-8; |
847 | ''; | 876 | ''; |
848 | root = pkgs.runCommand "mta-sts.${domain}" {} '' | 877 | root = pkgs.writeTextFile { |
849 | mkdir -p $out/.well-known | 878 | name = "mta-sts.${domain}"; |
850 | cp ${pkgs.writeText "mta-sts.${domain}.txt" '' | 879 | destination = "/.well-known/mta-sts.txt"; |
880 | text = '' | ||
851 | version: STSv1 | 881 | version: STSv1 |
852 | mode: enforce | 882 | mode: enforce |
853 | max_age: 2419200 | 883 | max_age: 2419200 |
854 | mx: mailin.${domain} | 884 | mx: mailin.${domain} |
855 | ''} $out/.well-known/mta-sts.txt | 885 | ''; |
856 | ''; | 886 | }; |
857 | }; | 887 | }; |
858 | }) emailDomains); | 888 | }) emailDomains); |
859 | }; | 889 | }; |
@@ -870,7 +900,7 @@ in { | |||
870 | systemd.services.spm = { | 900 | systemd.services.spm = { |
871 | serviceConfig = { | 901 | serviceConfig = { |
872 | Type = "notify"; | 902 | Type = "notify"; |
873 | ExecStart = "${pkgs.spm}/bin/spm-server"; | 903 | ExecStart = getExe' pkgs.spm "spm-server"; |
874 | User = "spm"; | 904 | User = "spm"; |
875 | Group = "spm"; | 905 | Group = "spm"; |
876 | 906 | ||
@@ -928,7 +958,7 @@ in { | |||
928 | serviceConfig = { | 958 | serviceConfig = { |
929 | Type = "notify"; | 959 | Type = "notify"; |
930 | 960 | ||
931 | ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; | 961 | ExecStart = getExe' ccert-policy-server "ccert-policy-server"; |
932 | 962 | ||
933 | Environment = [ | 963 | Environment = [ |
934 | "PGDATABASE=email" | 964 | "PGDATABASE=email" |
@@ -961,6 +991,53 @@ in { | |||
961 | }; | 991 | }; |
962 | users.groups."postfix-ccert-sender-policy" = {}; | 992 | users.groups."postfix-ccert-sender-policy" = {}; |
963 | 993 | ||
994 | systemd.sockets."postfix-internal-policy" = { | ||
995 | requiredBy = ["postfix.service"]; | ||
996 | wants = ["postfix-internal-policy.service"]; | ||
997 | socketConfig = { | ||
998 | ListenStream = "/run/postfix-internal-policy.sock"; | ||
999 | }; | ||
1000 | }; | ||
1001 | systemd.services."postfix-internal-policy" = { | ||
1002 | after = [ "postgresql.service" ]; | ||
1003 | bindsTo = [ "postgresql.service" ]; | ||
1004 | |||
1005 | serviceConfig = { | ||
1006 | Type = "notify"; | ||
1007 | |||
1008 | ExecStart = lib.getExe internal-policy-server; | ||
1009 | |||
1010 | Environment = [ | ||
1011 | "PGDATABASE=email" | ||
1012 | ]; | ||
1013 | |||
1014 | DynamicUser = false; | ||
1015 | User = "postfix-internal-policy"; | ||
1016 | Group = "postfix-internal-policy"; | ||
1017 | ProtectSystem = "strict"; | ||
1018 | SystemCallFilter = "@system-service"; | ||
1019 | NoNewPrivileges = true; | ||
1020 | ProtectKernelTunables = true; | ||
1021 | ProtectKernelModules = true; | ||
1022 | ProtectKernelLogs = true; | ||
1023 | ProtectControlGroups = true; | ||
1024 | MemoryDenyWriteExecute = true; | ||
1025 | RestrictSUIDSGID = true; | ||
1026 | KeyringMode = "private"; | ||
1027 | ProtectClock = true; | ||
1028 | RestrictRealtime = true; | ||
1029 | PrivateDevices = true; | ||
1030 | PrivateTmp = true; | ||
1031 | ProtectHostname = true; | ||
1032 | ReadWritePaths = ["/run/postgresql"]; | ||
1033 | }; | ||
1034 | }; | ||
1035 | users.users."postfix-internal-policy" = { | ||
1036 | isSystemUser = true; | ||
1037 | group = "postfix-internal-policy"; | ||
1038 | }; | ||
1039 | users.groups."postfix-internal-policy" = {}; | ||
1040 | |||
964 | services.postfwd = { | 1041 | services.postfwd = { |
965 | enable = true; | 1042 | enable = true; |
966 | cache = false; | 1043 | cache = false; |