{ config, lib, pkgs, ... }: with (import ./common.nix {inherit config;}); let cfg = config.mailsystem; nginxCfg = config.services.nginx; postfixCfg = config.services.postfix; redisCfg = config.services.redis.servers.rspamd; rspamdCfg = config.services.rspamd; genSystemdSocketCfg = name: socketPath: additionalUser: { description = "rspamd ${name} worker socket"; listenStreams = [socketPath]; requiredBy = ["rspamd.service"]; socketConfig = { Service = "rspamd.service"; SocketUser = rspamdCfg.user; SocketMode = 0600; ExecStartPost = lib.mkIf (additionalUser != "") ''${pkgs.acl.bin}/bin/setfacl -m "u:${additionalUser}:rw" "${socketPath}"''; }; }; in { options.mailsystem.rspamd.webUi = { enable = lib.mkOption { type = lib.types.bool; default = false; description = "Whether to enable the rspamd webui on `https://${config.mailsystem.fqdn}/rspamd`"; }; basicAuthFile = lib.mkOption { type = lib.types.str; description = "Path to basic auth file (entries can be generated using htpasswd)"; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = !cfg.rspamd.webUi.enable || cfg.rspamd.webUi.basicAuthFile != null; message = "Setting basicAuthFile is required if rspamd's web interface is enabled"; } ] ++ lib.mapAttrsToList ( domain: dkimList: { assertion = builtins.elem domain cfg.domains; message = "Domain ${domain} as per `config.mailsystem.dkimSettings` needs to be managed by the mailserver."; } ) cfg.dkimSettings ++ lib.mapAttrsToList ( domain: dkimList: { assertion = dkimList != []; message = "Entry ${domain} as per `config.mailsystem.dkimSettings` must not be an empty list."; } ) cfg.dkimSettings; services.rspamd = { enable = true; overrides = { "classifier-bayes.conf" = { text = '' autolearn { spam_threshold = 6.0 # When to learn spam (score >= threshold) ham_threshold = -2.0 # When to learn ham (score <= threshold) } ''; }; "dkim_signing.conf" = let genDkimSelectorList = entry: '' { path: "${entry.keyFile}"; selector: "${entry.selector}"; } ''; genDkimDomainCfg = domain: domainSettings: '' ${domain} { selectors [ ${lib.concatStringsSep "\n" (map genDkimSelectorList domainSettings)} ] } ''; in { text = '' sign_authenticated = true; use_esld = true; use_domain = "header"; check_pubkey = true; allow_username_mismatch = true; allow_hdrfrom_mismatch = true; allow_hdrfrom_mismatch_sign_networks = true; '' + lib.optionalString (cfg.dkimSettings != {}) '' domain { ${lib.concatStringsSep "\n" (lib.mapAttrsToList genDkimDomainCfg cfg.dkimSettings)} } ''; }; "milter_headers.conf" = { text = '' # Add headers related to spam-detection extended_spam_headers = true; ''; }; "redis.conf" = { text = '' servers = "${redisCfg.unixSocket}"; ''; }; "worker-controller.inc" = lib.mkIf cfg.rspamd.webUi.enable { text = '' secure_ip = "0.0.0.0/0"; secure_ip = "::/0"; ''; }; }; workers = { rspamd_proxy = { bindSockets = ["systemd:rspamd-proxy.socket"]; count = 1; # Do not spawn too many processes of this type extraConfig = '' milter = yes; # Enable milter mode timeout = 120s; # Needed for Milter usually upstream "local" { default = yes; # Self-scan upstreams are always default self_scan = yes; # Enable self-scan } ''; }; controller = { count = 1; bindSockets = ["systemd:rspamd-controller.socket"]; extraConfig = '' static_dir = "''${WWWDIR}"; # Serve the web UI static assets ''; }; }; }; systemd.sockets = { rspamd-proxy = genSystemdSocketCfg "proxy" rspamdProxySocket postfixCfg.user; rspamd-controller = genSystemdSocketCfg "controller" rspamdControllerSocket ( lib.optionalString cfg.rspamd.webUi.enable nginxCfg.user ); }; systemd.services.rspamd = { requires = ["redis-rspamd.service"]; after = ["redis-rspamd.service"]; }; services.nginx = lib.mkIf cfg.rspamd.webUi.enable { enable = true; virtualHosts."${cfg.fqdn}" = { forceSSL = true; locations."/rspamd" = { proxyPass = "http://unix:${rspamdControllerSocket}:/"; basicAuthFile = cfg.rspamd.webUi.basicAuthFile; extraConfig = '' proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ''; }; sslCertificate = lib.mkIf (cfg.certificateScheme == "selfsigned") sslCertPath; sslCertificateKey = lib.mkIf (cfg.certificateScheme == "selfsigned") sslKeyPath; }; }; }; }