at 21.11-pre 15 kB view raw
1{ config, options, pkgs, lib, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.rspamd; 8 postfixCfg = config.services.postfix; 9 10 bindSocketOpts = {options, config, ... }: { 11 options = { 12 socket = mkOption { 13 type = types.str; 14 example = "localhost:11333"; 15 description = '' 16 Socket for this worker to listen on in a format acceptable by rspamd. 17 ''; 18 }; 19 mode = mkOption { 20 type = types.str; 21 default = "0644"; 22 description = "Mode to set on unix socket"; 23 }; 24 owner = mkOption { 25 type = types.str; 26 default = "${cfg.user}"; 27 description = "Owner to set on unix socket"; 28 }; 29 group = mkOption { 30 type = types.str; 31 default = "${cfg.group}"; 32 description = "Group to set on unix socket"; 33 }; 34 rawEntry = mkOption { 35 type = types.str; 36 internal = true; 37 }; 38 }; 39 config.rawEntry = let 40 maybeOption = option: 41 optionalString options.${option}.isDefined " ${option}=${config.${option}}"; 42 in 43 if (!(hasPrefix "/" config.socket)) then "${config.socket}" 44 else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}"; 45 }; 46 47 traceWarning = w: x: builtins.trace "warning: ${w}" x; 48 49 workerOpts = { name, options, ... }: { 50 options = { 51 enable = mkOption { 52 type = types.nullOr types.bool; 53 default = null; 54 description = "Whether to run the rspamd worker."; 55 }; 56 name = mkOption { 57 type = types.nullOr types.str; 58 default = name; 59 description = "Name of the worker"; 60 }; 61 type = mkOption { 62 type = types.nullOr (types.enum [ 63 "normal" "controller" "fuzzy" "rspamd_proxy" "lua" "proxy" 64 ]); 65 description = '' 66 The type of this worker. The type <literal>proxy</literal> is 67 deprecated and only kept for backwards compatibility and should be 68 replaced with <literal>rspamd_proxy</literal>. 69 ''; 70 apply = let 71 from = "services.rspamd.workers.\"${name}\".type"; 72 files = options.type.files; 73 warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`"; 74 in x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x; 75 }; 76 bindSockets = mkOption { 77 type = types.listOf (types.either types.str (types.submodule bindSocketOpts)); 78 default = []; 79 description = '' 80 List of sockets to listen, in format acceptable by rspamd 81 ''; 82 example = [{ 83 socket = "/run/rspamd.sock"; 84 mode = "0666"; 85 owner = "rspamd"; 86 } "*:11333"]; 87 apply = value: map (each: if (isString each) 88 then if (isUnixSocket each) 89 then {socket = each; owner = cfg.user; group = cfg.group; mode = "0644"; rawEntry = "${each}";} 90 else {socket = each; rawEntry = "${each}";} 91 else each) value; 92 }; 93 count = mkOption { 94 type = types.nullOr types.int; 95 default = null; 96 description = '' 97 Number of worker instances to run 98 ''; 99 }; 100 includes = mkOption { 101 type = types.listOf types.str; 102 default = []; 103 description = '' 104 List of files to include in configuration 105 ''; 106 }; 107 extraConfig = mkOption { 108 type = types.lines; 109 default = ""; 110 description = "Additional entries to put verbatim into worker section of rspamd config file."; 111 }; 112 }; 113 config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") { 114 type = mkDefault name; 115 includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ]; 116 bindSockets = 117 let 118 unixSocket = name: { 119 mode = "0660"; 120 socket = "/run/rspamd/${name}.sock"; 121 owner = cfg.user; 122 group = cfg.group; 123 }; 124 in mkDefault (if name == "normal" then [(unixSocket "rspamd")] 125 else if name == "controller" then [ "localhost:11334" ] 126 else if name == "rspamd_proxy" then [ (unixSocket "proxy") ] 127 else [] ); 128 }; 129 }; 130 131 isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket); 132 133 mkBindSockets = enabled: socks: concatStringsSep "\n " 134 (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks)); 135 136 rspamdConfFile = pkgs.writeText "rspamd.conf" 137 '' 138 .include "$CONFDIR/common.conf" 139 140 options { 141 pidfile = "$RUNDIR/rspamd.pid"; 142 .include "$CONFDIR/options.inc" 143 .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc" 144 .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc" 145 } 146 147 logging { 148 type = "syslog"; 149 .include "$CONFDIR/logging.inc" 150 .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc" 151 .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc" 152 } 153 154 ${concatStringsSep "\n" (mapAttrsToList (name: value: let 155 includeName = if name == "rspamd_proxy" then "proxy" else name; 156 tryOverride = boolToString (value.extraConfig == ""); 157 in '' 158 worker "${value.type}" { 159 type = "${value.type}"; 160 ${optionalString (value.enable != null) 161 "enabled = ${if value.enable != false then "yes" else "no"};"} 162 ${mkBindSockets value.enable value.bindSockets} 163 ${optionalString (value.count != null) "count = ${toString value.count};"} 164 ${concatStringsSep "\n " (map (each: ".include \"${each}\"") value.includes)} 165 .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc" 166 .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc" 167 } 168 '') cfg.workers)} 169 170 ${optionalString (cfg.extraConfig != "") '' 171 .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc" 172 ''} 173 ''; 174 175 filterFiles = files: filterAttrs (n: v: v.enable) files; 176 rspamdDir = pkgs.linkFarm "etc-rspamd-dir" ( 177 (mapAttrsToList (name: file: { name = "local.d/${name}"; path = file.source; }) (filterFiles cfg.locals)) ++ 178 (mapAttrsToList (name: file: { name = "override.d/${name}"; path = file.source; }) (filterFiles cfg.overrides)) ++ 179 (optional (cfg.localLuaRules != null) { name = "rspamd.local.lua"; path = cfg.localLuaRules; }) ++ 180 [ { name = "rspamd.conf"; path = rspamdConfFile; } ] 181 ); 182 183 configFileModule = prefix: { name, config, ... }: { 184 options = { 185 enable = mkOption { 186 type = types.bool; 187 default = true; 188 description = '' 189 Whether this file ${prefix} should be generated. This 190 option allows specific ${prefix} files to be disabled. 191 ''; 192 }; 193 194 text = mkOption { 195 default = null; 196 type = types.nullOr types.lines; 197 description = "Text of the file."; 198 }; 199 200 source = mkOption { 201 type = types.path; 202 description = "Path of the source file."; 203 }; 204 }; 205 config = { 206 source = mkIf (config.text != null) ( 207 let name' = "rspamd-${prefix}-" + baseNameOf name; 208 in mkDefault (pkgs.writeText name' config.text)); 209 }; 210 }; 211 212 configOverrides = 213 (mapAttrs' (n: v: nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" { 214 text = v.extraConfig; 215 }) 216 (filterAttrs (n: v: v.extraConfig != "") cfg.workers)) 217 // (if cfg.extraConfig == "" then {} else { 218 "extra-config.inc".text = cfg.extraConfig; 219 }); 220in 221 222{ 223 ###### interface 224 225 options = { 226 227 services.rspamd = { 228 229 enable = mkEnableOption "rspamd, the Rapid spam filtering system"; 230 231 debug = mkOption { 232 type = types.bool; 233 default = false; 234 description = "Whether to run the rspamd daemon in debug mode."; 235 }; 236 237 locals = mkOption { 238 type = with types; attrsOf (submodule (configFileModule "locals")); 239 default = {}; 240 description = '' 241 Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>. 242 ''; 243 example = literalExample '' 244 { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf"; 245 "arc.conf".text = "allow_envfrom_empty = true;"; 246 } 247 ''; 248 }; 249 250 overrides = mkOption { 251 type = with types; attrsOf (submodule (configFileModule "overrides")); 252 default = {}; 253 description = '' 254 Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>. 255 ''; 256 example = literalExample '' 257 { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf"; 258 "arc.conf".text = "allow_envfrom_empty = true;"; 259 } 260 ''; 261 }; 262 263 localLuaRules = mkOption { 264 default = null; 265 type = types.nullOr types.path; 266 description = '' 267 Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local 268 rules written in Lua 269 ''; 270 }; 271 272 workers = mkOption { 273 type = with types; attrsOf (submodule workerOpts); 274 description = '' 275 Attribute set of workers to start. 276 ''; 277 default = { 278 normal = {}; 279 controller = {}; 280 }; 281 example = literalExample '' 282 { 283 normal = { 284 includes = [ "$CONFDIR/worker-normal.inc" ]; 285 bindSockets = [{ 286 socket = "/run/rspamd/rspamd.sock"; 287 mode = "0660"; 288 owner = "${cfg.user}"; 289 group = "${cfg.group}"; 290 }]; 291 }; 292 controller = { 293 includes = [ "$CONFDIR/worker-controller.inc" ]; 294 bindSockets = [ "[::1]:11334" ]; 295 }; 296 } 297 ''; 298 }; 299 300 extraConfig = mkOption { 301 type = types.lines; 302 default = ""; 303 description = '' 304 Extra configuration to add at the end of the rspamd configuration 305 file. 306 ''; 307 }; 308 309 user = mkOption { 310 type = types.str; 311 default = "rspamd"; 312 description = '' 313 User to use when no root privileges are required. 314 ''; 315 }; 316 317 group = mkOption { 318 type = types.str; 319 default = "rspamd"; 320 description = '' 321 Group to use when no root privileges are required. 322 ''; 323 }; 324 325 postfix = { 326 enable = mkOption { 327 type = types.bool; 328 default = false; 329 description = "Add rspamd milter to postfix main.conf"; 330 }; 331 332 config = mkOption { 333 type = with types; attrsOf (oneOf [ bool str (listOf str) ]); 334 description = '' 335 Addon to postfix configuration 336 ''; 337 default = { 338 smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"]; 339 non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"]; 340 }; 341 example = { 342 smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"]; 343 non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"]; 344 }; 345 }; 346 }; 347 }; 348 }; 349 350 351 ###### implementation 352 353 config = mkIf cfg.enable { 354 services.rspamd.overrides = configOverrides; 355 services.rspamd.workers = mkIf cfg.postfix.enable { 356 controller = {}; 357 rspamd_proxy = { 358 bindSockets = [ { 359 mode = "0660"; 360 socket = "/run/rspamd/rspamd-milter.sock"; 361 owner = cfg.user; 362 group = postfixCfg.group; 363 } ]; 364 extraConfig = '' 365 upstream "local" { 366 default = yes; # Self-scan upstreams are always default 367 self_scan = yes; # Enable self-scan 368 } 369 ''; 370 }; 371 }; 372 services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config; 373 374 systemd.services.postfix.serviceConfig.SupplementaryGroups = 375 mkIf cfg.postfix.enable [ postfixCfg.group ]; 376 377 # Allow users to run 'rspamc' and 'rspamadm'. 378 environment.systemPackages = [ pkgs.rspamd ]; 379 380 users.users.${cfg.user} = { 381 description = "rspamd daemon"; 382 uid = config.ids.uids.rspamd; 383 group = cfg.group; 384 }; 385 386 users.groups.${cfg.group} = { 387 gid = config.ids.gids.rspamd; 388 }; 389 390 environment.etc.rspamd.source = rspamdDir; 391 392 systemd.services.rspamd = { 393 description = "Rspamd Service"; 394 395 wantedBy = [ "multi-user.target" ]; 396 after = [ "network.target" ]; 397 restartTriggers = [ rspamdDir ]; 398 399 serviceConfig = { 400 ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} -c /etc/rspamd/rspamd.conf -f"; 401 Restart = "always"; 402 403 User = "${cfg.user}"; 404 Group = "${cfg.group}"; 405 SupplementaryGroups = mkIf cfg.postfix.enable [ postfixCfg.group ]; 406 407 RuntimeDirectory = "rspamd"; 408 RuntimeDirectoryMode = "0755"; 409 StateDirectory = "rspamd"; 410 StateDirectoryMode = "0700"; 411 412 AmbientCapabilities = []; 413 CapabilityBoundingSet = ""; 414 DevicePolicy = "closed"; 415 LockPersonality = true; 416 NoNewPrivileges = true; 417 PrivateDevices = true; 418 PrivateMounts = true; 419 PrivateTmp = true; 420 # we need to chown socket to rspamd-milter 421 PrivateUsers = !cfg.postfix.enable; 422 ProtectClock = true; 423 ProtectControlGroups = true; 424 ProtectHome = true; 425 ProtectHostname = true; 426 ProtectKernelLogs = true; 427 ProtectKernelModules = true; 428 ProtectKernelTunables = true; 429 ProtectSystem = "strict"; 430 RemoveIPC = true; 431 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; 432 RestrictNamespaces = true; 433 RestrictRealtime = true; 434 RestrictSUIDSGID = true; 435 SystemCallArchitectures = "native"; 436 SystemCallFilter = "@system-service"; 437 UMask = "0077"; 438 }; 439 }; 440 }; 441 imports = [ 442 (mkRemovedOptionModule [ "services" "rspamd" "socketActivation" ] 443 "Socket activation never worked correctly and could at this time not be fixed and so was removed") 444 (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ]) 445 (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ]) 446 (mkRemovedOptionModule [ "services" "rmilter" ] "Use services.rspamd.* instead to set up milter service") 447 ]; 448}