{ config, pkgs, lib, flake, flakeInputs, ... }: with lib; let dovecotSievePipeBin = pkgs.stdenv.mkDerivation { name = "dovecot-sieve-pipe-bin"; src = ./dovecot-pipe-bin; buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ]; buildCommand = '' mkdir -p $out/pipe/bin cp $src/* $out/pipe/bin/ chmod a+x $out/pipe/bin/* patchShebangs $out/pipe/bin for file in $out/pipe/bin/*; do wrapProgram $file \ --set PATH "${makeBinPath (with pkgs; [coreutils rspamd])}" done ''; }; ccert-policy-server = let workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./ccert-policy-server; }; pythonSet = flake.lib.pythonSet { inherit pkgs; python = pkgs.python312; overlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; }; }; virtualEnv = pythonSet.mkVirtualEnv "ccert-policy-server-env" workspace.deps.default; in virtualEnv.overrideAttrs (oldAttrs: { meta = (oldAttrs.meta or {}) // { mainProgram = "ccert-policy-server"; }; }); internal-policy-server = let workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./internal-policy-server; }; pythonSet = flake.lib.pythonSet { inherit pkgs; python = pkgs.python312; overlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; }; }; virtualEnv = pythonSet.mkVirtualEnv "internal-policy-server-env" workspace.deps.default; in virtualEnv.overrideAttrs (oldAttrs: { meta = (oldAttrs.meta or {}) // { mainProgram = "internal-policy-server"; }; }); nftables-nologin-script = pkgs.resholve.writeScript "nftables-mail-nologin" { inputs = with pkgs; [inetutils nftables gnugrep findutils]; interpreter = lib.getExe pkgs.zsh; } '' set -e typeset -a as_sets mnt_bys route route6 as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) mnt_bys=(${lib.escapeShellArgs config.services.email.nologin.MNTBys}) for as_set in $as_sets; do while IFS=$'\n' read line; do if [[ "''${line}" =~ "^route:\s+(.+)$" ]]; then route+=($match[1]) elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then route6+=($match[1]) fi done < <(whois -h whois.radb.net "!i''${as_set},1" | grep -Eo 'AS[0-9]+' | xargs whois -h whois.radb.net -- -i origin) done for mnt_by in $mnt_bys; do while IFS=$'\n' read line; do if [[ "''${line}" =~ "^route:\s+(.+)$" ]]; then route+=($match[1]) elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then route6+=($match[1]) fi done < <(whois -h whois.radb.net "!o''${mnt_by}") done printf -v elements4 '%s,' "''${route[@]}" elements4=''${elements4%,} printf -v elements6 '%s,' "''${route6[@]}" elements6=''${elements6%,} nft -f - < Subject: Undelivered Mail Returned to Sender Postmaster-Subject: Postmaster Copy: Undelivered Mail This is the mail system at host $myhostname. I'm sorry to have to inform you that your message could not be delivered to one or more recipients. It's attached below. The mail system ''); delay_template_file = toString (pkgs.writeText "delay.cf" '' Charset: us-ascii From: Mail Delivery System Subject: Delayed Mail (still being retried) Postmaster-Subject: Postmaster Warning: Delayed Mail This is the mail system at host $myhostname. #################################################################### # THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. # #################################################################### Your message could not be delivered for more than $delay_warning_time_minutes minute(s). It will be retried until it is $maximal_queue_lifetime_minutes minute(s) old. The mail system ''); smtpd_discard_ehlo_keyword_address_maps = "cidr:${pkgs.writeText "esmtp_access" '' # Allow DSN requests from local subnet only 192.168.0.0/16 silent-discard 172.16.0.0/12 silent-discard 10.0.0.0/8 silent-discard 0.0.0.0/0 silent-discard, dsn fd00::/8 silent-discard ::/0 silent-discard, dsn ''}"; virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' hosts = postgresql:///email dbname = email query = SELECT 1 FROM virtual_mailbox_domain WHERE domain = '%s' ''}''; virtual_mailbox_maps = ''pgsql:${pkgs.writeText "virtual_mailbox_maps.cf" '' hosts = postgresql:///email dbname = email query = SELECT 1 FROM virtual_mailbox_mapping WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_mapping WHERE lookup = '%s')) ''}''; dvlmtp_destination_recipient_limit = "1"; virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; smtputf8_enable = false; authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.settings.mail_uid}= }"; authorized_flush_users = "inline:{ root= }"; authorized_mailq_users = "inline:{ root= }"; postscreen_access_list = ""; postscreen_denylist_action = "drop"; postscreen_greet_action = "enforce"; sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" '' hosts = postgresql:///email dbname = email query = SELECT value FROM sender_bcc_maps WHERE key = '%s' ''}''; recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" '' hosts = postgresql:///email dbname = email query = SELECT value FROM recipient_bcc_maps WHERE key = '%s' ''}''; }; settings.master = { "465" = { type = "inet"; private = false; command = "smtpd -v"; args = [ "-o" "smtpd_tls_security_level=encrypt" "-o" "{smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" "-o" "{smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" "-o" "smtpd_tls_mandatory_ciphers=high" "-o" "{tls_eecdh_auto_curves = X25519 X448}" "-o" "smtpd_tls_wrappermode=yes" "-o" "smtpd_tls_ask_ccert=yes" "-o" "smtpd_tls_req_ccert=yes" "-o" "smtpd_tls_received_header=no" "-o" "cleanup_service_name=subcleanup" "-o" "smtpd_client_restrictions=permit_tls_all_clientcerts,reject" "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' hosts = postgresql:///email dbname = email 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')) ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}'' "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" "-o" "unverified_sender_reject_code=550" "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" ]; }; "466" = { type = "inet"; private = false; command = "smtpd -v"; args = [ "-o" "smtpd_tls_security_level=encrypt" "-o" "smtpd_tls_wrappermode=yes" "-o" "smtpd_tls_ask_ccert=no" "-o" "smtpd_tls_req_ccert=no" "-o" "smtpd_sasl_type=dovecot" "-o" "smtpd_sasl_path=/run/dovecot-sasl" "-o" "smtpd_sasl_auth_enable=yes" "-o" "smtpd_tls_received_header=no" "-o" "cleanup_service_name=subcleanup" "-o" "smtpd_client_restrictions=permit_sasl_authenticated,reject" "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' hosts = postgresql:///email dbname = email 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')) ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}'' "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" "-o" "unverified_sender_reject_code=550" "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" ]; }; subcleanup = { command = "cleanup -v"; private = false; maxproc = 0; args = [ "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' if /^Received: / !/by surtr\.yggdrasil\.li/ STRIP /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 endif ''}" ]; }; dvlmtp = { command = "lmtp"; args = [ "flags=DORX" ]; }; smtp_pass = { name = "smtpd"; type = "pass"; command = "smtpd -v"; }; postscreen = { name = "smtp"; type = "inet"; private = false; command = "postscreen -v"; maxproc = 1; }; smtp = {}; relay = { command = "smtp"; args = [ "-o" "smtp_fallback_relay=" ]; }; tlsproxy = { maxproc = 0; }; dnsblog = {}; }; }; services.postsrsd = { enable = true; domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; separator = "+"; extraConfig = '' socketmap = unix:/run/postsrsd/postsrsd-socketmap.sock milter = unix:/run/postsrsd/postsrsd-milter.sock ''; }; services.rspamd = { enable = true; workers = { controller = { type = "controller"; count = 1; bindSockets = [ { mode = "0660"; socket = "/run/rspamd/worker-controller.sock"; owner = config.services.rspamd.user; group = config.services.rspamd.group; } ]; includes = []; extraConfig = '' static_dir = "''${WWWDIR}"; # Serve the web UI static assets ''; }; external = { type = "rspamd_proxy"; bindSockets = [ { mode = "0660"; socket = "/run/rspamd/rspamd-milter.sock"; owner = config.services.rspamd.user; group = config.services.rspamd.group; } ]; extraConfig = '' milter = yes; timeout = 120s; client_ca_name = "yggdrasil.li"; upstream "local" { default = yes; self_scan = yes; } ''; }; }; locals = { "milter_headers.conf".text = '' use = ["authentication-results", "x-spamd-result", "x-rspamd-queue-id", "x-rspamd-server", "x-spam-level", "x-spam-status"]; extended_headers_rcpt = []; ''; "actions.conf".text = '' reject = 15; add_header = 10; greylist = 5; ''; "groups.conf".text = '' symbols { "BAYES_SPAM" { weight = 2.0; } } ''; "dmarc.conf".text = '' reporting = true; send_reports = true; report_settings { org_name = "Yggdrasil.li"; domain = "yggdrasil.li"; email = "postmaster@yggdrasil.li"; } ''; "redis.conf".text = '' servers = "${config.services.redis.servers.rspamd.unixSocket}"; ''; "dkim_signing.conf".text = '' enabled = true; allow_username_mismatch = true; path = "/var/lib/rspamd/dkim/$domain.key"; selector = "mail"; ''; "neural.conf".text = "enabled = false;"; "classifier-bayes.conf".text = '' enable = true; expire = 8640000; new_schema = true; backend = "redis"; per_user = true; min_learns = 0; autolearn = [0, 10]; statfile { symbol = "BAYES_HAM"; spam = false; } statfile { symbol = "BAYES_SPAM"; spam = true; } ''; "logging.inc".text = '' debug_modules = ["milter", "dkim_signing"]; ''; # "redirectors.inc".text = '' # visit.creeper.host # ''; }; }; users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.settings.mail_uid ]; services.redis.servers.rspamd.enable = true; users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; environment.systemPackages = with pkgs; [ dovecot_pigeonhole ]; services.dovecot2 = { package = pkgs.dovecot; enable = true; enablePAM = false; settings = { dovecot_config_version = "2.4.2"; dovecot_storage_version = "2.4.0"; sql_driver = "pgsql"; "pgsql /run/postgresql".parameters = { dbname = "email"; }; protocols = { imap = true; lmtp = true; sieve = true; }; mail_plugins = { quota = true; fts = true; fts_flatcurve = true; }; mail_uid = "dovecot2"; mail_gid = "dovecot2"; first_valid_uid = config.ids.uids.dovecot2; last_valid_uid = config.ids.uids.dovecot2; first_valid_gid = config.ids.gids.dovecot2; last_valid_gid = config.ids.gids.dovecot2; mail_driver = "maildir"; mail_path = "/var/lib/mail/%{user}/maildir"; mail_index_path = "/var/lib/dovecot/indices/%{user}"; ssl_server_ca_file = ./ca/ca.crt; ssl_server_key_file = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem"; ssl_server_cert_file = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem"; mail_home = "/var/lib/mail/%{user}"; ssl_server_require_crl = false; ssl_server_request_client_cert = true; ssl_min_protocol = "TLSv1.2"; ssl_curve_list = "X25519MLKEM768:X25519"; ssl_cipher_list = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"; auth_ssl_username_from_cert = "yes"; ssl_server_cert_username_field = "commonName"; auth_mechanisms = ["plain" "login" "external"]; log_debug = "category=ssl OR category=auth"; auth_verbose = true; "service auth-worker".user = "$SET:default_internal_user"; "userdb prefetch" = {}; "userdb sql" = { sql_query = "SELECT \"user\", quota_rule, '${config.services.dovecot2.settings.mail_uid}' as uid, 'dovecot2' as gid FROM imap_user WHERE \"user\" = '%{user | username}'"; sql_iterate_query = "SELECT \"user\" FROM imap_user"; fields = { uid = "$SET:default_internal_user"; gid = "$SET:default_internal_user"; }; }; "passdb sql" = { sql_query = '' SELECT (CASE WHEN '%{cert}' = 'valid' AND '%{mechanism}' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%{cert}' = 'valid' AND '%{mechanism}' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, '${config.services.dovecot2.settings.mail_uid}' as uid, '${config.services.dovecot2.settings.mail_gid}' as gid FROM imap_user WHERE "user" = '%{user | username}' ''; }; "protocol lmtp" = { mail_plugins.sieve = true; "userdb sql-lmtp" = { driver = "sql"; sql_query = '' SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, '${config.services.dovecot2.settings.mail_uid}' as uid, '${config.services.dovecot2.settings.mail_gid}' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%{user | username}' :: citext) = local || '+' || extension AND domain = ('%{user | domain}' :: citext) WHEN local IS NOT NULL THEN (local = ('%{user | username}' :: citext) OR ('%{user | username}' :: citext) ILIKE local || '+%%') AND domain = ('%{user | domain}' :: citext) WHEN extension IS NOT NULL THEN ('%{user | username}' :: citext) ILIKE '%%+' || extension AND domain = ('%{user | domain}' :: citext) ELSE domain = ('%{user | domain}' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC ''; skip = "never"; result_failure = "return-fail"; result_internalfail = "return-fail"; }; }; mailbox_list_index = true; mailbox_list_utf8 = true; postmaster_address = "postmaster@yggdrasil.li"; recipient_delimiter = null; auth_username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-+_@"; "service lmtp" = { vsz_limit = "1G"; "unix_listener /run/dovecot-lmtp" = { mode = "0600"; user = "postfix"; group = "postfix"; }; }; "service auth" = { vsz_limit = "2G"; "unix_listener /run/dovecot-sasl" = { mode = "0600"; user = "postfix"; group = "postfix"; }; }; quota_storage_size = "1G"; "namespace inbox" = { separator = "/"; inbox = true; "mailbox Trash" = { auto = false; special_use = "\\Trash"; quota_storage_percentage = "110"; }; "mailbox Junk" = { auto = false; special_use = "\\Junk"; }; "mailbox Drafts" = { auto = false; special_use = "\\Drafts"; }; "mailbox Sent" = { auto = "subscribe"; special_use = "\\Sent"; }; "mailbox \"Sent Messages\"" = { auto = false; special_use = "\\Sent"; }; }; quota_status_overquota = "552 5.2.2 Mailbox is full"; quota_status_success = "DUNNO"; quota_status_nouser = "DUNNO"; quota_storage_grace = "100M"; quota_mail_size = 10 * 1024 * 1024 * 1024; sieve_plugins = { "sieve_imapsieve" = true; "sieve_extprograms" = true; }; sieve_redirect_envelope_from = "orig_recipient"; sieve_extensions = { imapsieve = true; vacation-seconds = true; "vnd.dovecot.debug" = true; }; sieve_global_extensions = { "vnd.dovecot.pipe" = true; "vnd.dovecot.environment" = true; }; sieve_pipe_bin_dir = "${dovecotSievePipeBin}/pipe/bin"; "sieve_script before" = { type = "before"; path = "/etc/dovecot/sieve_before.d"; }; "sieve_script flag" = { type = "before"; cause.flag = true; path = "/etc/dovecot/sieve_flag.d"; }; "fts flatcurve" = { autoindex = true; }; language_tokenizers = ["generic" "email-address"]; language_filters = ["normalizer-icu" "snowball" "stopwords"]; "language en" = { default = true; filters = ["lowercase" "snowball" "stopwords"]; }; "language de" = {}; "protocol imap" = { mail_max_userip_connections = 50; mail_plugins = { imap_quota = true; imap_sieve = true; }; }; "service imap-login"."inet_listener imap".port = 0; "service managesieve-login"."inet_listener sieve".port = 4190; "service indexer-worker".vsz_limit = 1024 * 1024 * 1024; } // (genAttrs' (concatMap (domain: ["imap.${domain}" domain]) emailDomains) (subdomain: nameValuePair "local_name ${subdomain}" { ssl_server_key_file = "/run/credentials/dovecot.service/${subdomain}.key.pem"; ssl_server_cert_file = "/run/credentials/dovecot.service/${subdomain}.pem"; })); }; environment.etc = { "dovecot/sieve_before.d/tag-junk.sieve".text = '' require ["imap4flags"]; if header :contains "X-Spam-Flag" "YES" { addflag ["\\Junk"]; } ''; "dovecot/sieve_flag.d/learn-junk.sieve".text = '' require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables", "imap4flags"]; if environment :matches "imap.user" "*" { set "username" "''${1}"; } if environment :contains "imap.changedflags" "\\Junk" { if hasflag "\\Junk" { pipe :copy "learn_spam.sh" [ "''${username}" ]; } else { if environment :matches "imap.mailbox" "*" { set "mailbox" "''${1}"; } if not anyof(string "''${mailbox}" "Trash", string "''${mailbox}" "Junk") { pipe :copy "learn_ham.sh" [ "''${username}" ]; } } } ''; }; security.acme.rfc2136Domains = { "surtr.yggdrasil.li" = { restartUnits = [ "postfix.service" "dovecot.service" ]; }; } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) // listToAttrs (concatMap (domain: [ (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; }) (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; }) (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) ]) emailDomains); systemd.services.postfix = { serviceConfig.LoadCredential = let tlsCredential = domain: "${domain}.full.pem:${config.security.acme.certs.${domain}.directory}/full.pem"; in [ (tlsCredential "surtr.yggdrasil.li") ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains; }; systemd.services.dovecot = { serviceConfig = { LoadCredential = [ "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" ] ++ concatMap (domain: concatMap (subdomain: [ "${subdomain}.key.pem:${config.security.acme.certs.${subdomain}.directory}/key.pem" "${subdomain}.pem:${config.security.acme.certs.${subdomain}.directory}/fullchain.pem" ]) [domain "imap.${domain}"] ) emailDomains; }; }; services.nginx = { upstreams.spm = { servers = { "unix:/run/spm/server.sock" = {}; }; }; virtualHosts = listToAttrs (map (domain: nameValuePair "spm.${domain}" { forceSSL = true; kTLS = true; http3 = false; sslCertificate = "/run/credentials/nginx.service/spm.${domain}.pem"; sslCertificateKey = "/run/credentials/nginx.service/spm.${domain}.key.pem"; extraConfig = '' ssl_stapling off; ssl_verify_client on; ssl_client_certificate ${toString ./ca/ca.crt}; ''; locations."/" = { proxyPass = "http://spm"; extraConfig = '' proxy_set_header SSL-CLIENT-VERIFY $ssl_client_verify; proxy_set_header SSL-CLIENT-S-DN $ssl_client_s_dn; proxy_set_header SPM-DOMAIN "${domain}"; ''; }; }) spmDomains) // listToAttrs (map (domain: nameValuePair "mta-sts.${domain}" { forceSSL = true; kTLS = true; http3 = true; sslCertificate = "/run/credentials/nginx.service/mta-sts.${domain}.pem"; sslCertificateKey = "/run/credentials/nginx.service/mta-sts.${domain}.key.pem"; sslTrustedCertificate = "/run/credentials/nginx.service/mta-sts.${domain}.chain.pem"; extraConfig = '' add_header Strict-Transport-Security "max-age=63072000" always; add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type, Authorization'; add_header Access-Control-Max-Age 7200; ''; locations."/" = { extraConfig = '' charset utf-8; source_charset utf-8; ''; root = pkgs.writeTextFile { name = "mta-sts.${domain}"; destination = "/.well-known/mta-sts.txt"; text = '' version: STSv1 mode: enforce max_age: 2419200 mx: mailin.${domain} ''; }; }; }) emailDomains); }; systemd.services.nginx.serviceConfig.LoadCredential = concatMap (domain: [ "spm.${domain}.key.pem:${config.security.acme.certs."spm.${domain}".directory}/key.pem" "spm.${domain}.pem:${config.security.acme.certs."spm.${domain}".directory}/fullchain.pem" ]) spmDomains ++ concatMap (domain: [ "mta-sts.${domain}.key.pem:${config.security.acme.certs."mta-sts.${domain}".directory}/key.pem" "mta-sts.${domain}.pem:${config.security.acme.certs."mta-sts.${domain}".directory}/fullchain.pem" "mta-sts.${domain}.chain.pem:${config.security.acme.certs."mta-sts.${domain}".directory}/chain.pem" ]) emailDomains; systemd.services.spm = { serviceConfig = { Type = "notify"; ExecStart = getExe' pkgs.spm "spm-server"; User = "spm"; Group = "spm"; Environment = [ "SPM_INSTANCE=ed1c0e1d-7be4-4dd5-b51a-291bad3ac9c9" "PGCONNSTR=dbname=email" ]; LoadCredential = [ "spm-keys.json:${config.sops.secrets."spm-keys.json".path}" ]; }; }; systemd.sockets.spm = { wantedBy = [ "nginx.service" ]; socketConfig = { ListenStream = "/run/spm/server.sock"; SocketUser = "spm"; SocketGroup = "spm"; SocketMode = 0660; }; }; users.users.spm = { isSystemUser = true; group = "spm"; }; users.groups.spm = { members = [ config.services.nginx.user ]; }; sops.secrets."spm-keys.json" = { format = "binary"; sopsFile = ./spm-keys.json; }; services.postfix-mta-sts-resolver = { enable = true; loglevel = "debug"; }; systemd.sockets."postfix-ccert-sender-policy" = { requiredBy = ["postfix.service"]; wants = ["postfix-ccert-sender-policy.service"]; socketConfig = { ListenStream = "/run/postfix-ccert-sender-policy.sock"; }; }; systemd.services."postfix-ccert-sender-policy" = { after = [ "postgresql.service" ]; bindsTo = [ "postgresql.service" ]; serviceConfig = { Type = "notify"; ExecStart = getExe' ccert-policy-server "ccert-policy-server"; Environment = [ "PGDATABASE=email" ]; DynamicUser = false; User = "postfix-ccert-sender-policy"; Group = "postfix-ccert-sender-policy"; ProtectSystem = "strict"; SystemCallFilter = "@system-service"; NoNewPrivileges = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; MemoryDenyWriteExecute = true; RestrictSUIDSGID = true; KeyringMode = "private"; ProtectClock = true; RestrictRealtime = true; PrivateDevices = true; PrivateTmp = true; ProtectHostname = true; ReadWritePaths = ["/run/postgresql"]; }; }; users.users."postfix-ccert-sender-policy" = { isSystemUser = true; group = "postfix-ccert-sender-policy"; }; users.groups."postfix-ccert-sender-policy" = {}; systemd.sockets."postfix-internal-policy" = { requiredBy = ["postfix.service"]; wants = ["postfix-internal-policy.service"]; socketConfig = { ListenStream = "/run/postfix-internal-policy.sock"; }; }; systemd.services."postfix-internal-policy" = { after = [ "postgresql.service" ]; bindsTo = [ "postgresql.service" ]; serviceConfig = { Type = "notify"; ExecStart = lib.getExe internal-policy-server; Environment = [ "PGDATABASE=email" ]; DynamicUser = false; User = "postfix-internal-policy"; Group = "postfix-internal-policy"; ProtectSystem = "strict"; SystemCallFilter = "@system-service"; NoNewPrivileges = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; MemoryDenyWriteExecute = true; RestrictSUIDSGID = true; KeyringMode = "private"; ProtectClock = true; RestrictRealtime = true; PrivateDevices = true; PrivateTmp = true; ProtectHostname = true; ReadWritePaths = ["/run/postgresql"]; }; }; users.users."postfix-internal-policy" = { isSystemUser = true; group = "postfix-internal-policy"; }; users.groups."postfix-internal-policy" = {}; services.postfwd = { enable = true; cache = false; rules = '' id=RCPT_SASL01; protocol_state=DATA; protocol_state=END-OF-MESSAGE; sasl_username!=; action=rcpt(sasl_username/100/3600/set(HIT_RATELIMIT=1,HIT_RATECOUNT=$$ratecount,HIT_RATELIMIT_LIMIT=100,HIT_RATELIMIT_INTERVAL=3600)) id=RCPT_SASL02; protocol_state=DATA; protocol_state=END-OF-MESSAGE; sasl_username!=; action=rcpt(sasl_username/1000/86400/set(HIT_RATELIMIT=1,HIT_RATECOUNT=$$ratecount,HIT_RATELIMIT_LIMIT=1000,HIT_RATELIMIT_INTERVAL=86400)) id=RCPT_CCERT01; protocol_state=DATA; protocol_state=END-OF-MESSAGE; ccert_subject!=; action=rcpt(ccert_subject/100/3600/set(HIT_RATELIMIT=1,HIT_RATECOUNT=$$ratecount,HIT_RATELIMIT_LIMIT=100,HIT_RATELIMIT_INTERVAL=3600)) id=RCPT_CCERT02; protocol_state=DATA; protocol_state=END-OF-MESSAGE; ccert_subject!=; action=rcpt(ccert_subject/1000/86400/set(HIT_RATELIMIT=1,HIT_RATECOUNT=$$ratecount,HIT_RATELIMIT_LIMIT=1000,HIT_RATELIMIT_INTERVAL=86400)) id=JUMP_REJECT_RL; HIT_RATELIMIT=="1"; action=jump(REJECT_RL) id=EOF; action=DUNNO id=REJECT_RL; action=450 4.7.1 Exceeding maximum of $$HIT_RATELIMIT_LIMIT recipients per $$HIT_RATELIMIT_INTERVAL seconds [$$HIT_RATECOUNT] ''; }; services.email.nologin.MNTBys = ["MICROSOFT-MAINT"]; systemd.services.nftables.serviceConfig = { ExecStart = lib.mkAfter [ nftables-nologin-script ]; ExecReload = lib.mkAfter [ nftables-nologin-script ]; }; systemd.services."nftables-mail-nologin" = { serviceConfig = { Type = "oneshot"; ExecStart = nftables-nologin-script; }; }; systemd.timers."nftables-mail-nologin" = { wantedBy = [ "nftables.service" ]; timerConfig = { OnActiveSec = "20h"; RandomizedDelaySec = "8h"; }; }; }; }