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