nixpkgs/nixos/modules/services/mail/rspamd.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

548 lines
16 KiB
Nix
Raw Normal View History

{
config,
options,
pkgs,
lib,
...
}:
2016-01-12 11:06:46 +02:00
with lib;
let
cfg = config.services.rspamd;
opt = options.services.rspamd;
postfixCfg = config.services.postfix;
2016-01-12 11:06:46 +02:00
bindSocketOpts =
{ options, config, ... }:
{
options = {
socket = mkOption {
type = types.str;
example = "localhost:11333";
description = ''
Socket for this worker to listen on in a format acceptable by rspamd.
'';
};
mode = mkOption {
type = types.str;
default = "0644";
description = "Mode to set on unix socket";
};
owner = mkOption {
type = types.str;
default = "${cfg.user}";
description = "Owner to set on unix socket";
};
group = mkOption {
type = types.str;
default = "${cfg.group}";
description = "Group to set on unix socket";
};
rawEntry = mkOption {
type = types.str;
internal = true;
};
};
config.rawEntry =
let
maybeOption = option: optionalString options.${option}.isDefined " ${option}=${config.${option}}";
in
if (!(hasPrefix "/" config.socket)) then
"${config.socket}"
else
"${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
};
traceWarning = w: x: builtins.trace "warning: ${w}" x;
workerOpts =
{ name, options, ... }:
{
options = {
enable = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Whether to run the rspamd worker.";
};
name = mkOption {
type = types.nullOr types.str;
default = name;
description = "Name of the worker";
};
type = mkOption {
type = types.nullOr (
types.enum [
2019-11-18 13:56:56 +01:00
"normal"
"controller"
"fuzzy"
"rspamd_proxy"
"lua"
"proxy"
]
);
description = ''
The type of this worker. The type `proxy` is
deprecated and only kept for backwards compatibility and should be
replaced with `rspamd_proxy`.
'';
apply =
let
2019-09-16 23:40:32 +00:00
from = "services.rspamd.workers.\"${name}\".type";
files = options.type.files;
warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
in
x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
};
bindSockets = mkOption {
type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
default = [ ];
description = ''
List of sockets to listen, in format acceptable by rspamd
'';
example = [
{
socket = "/run/rspamd.sock";
mode = "0666";
owner = "rspamd";
}
"*:11333"
];
apply =
value:
map (
each:
if (isString each) then
if (isUnixSocket each) then
{
socket = each;
owner = cfg.user;
group = cfg.group;
mode = "0644";
rawEntry = "${each}";
}
else
{
socket = each;
rawEntry = "${each}";
}
else
each
) value;
};
count = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
Number of worker instances to run
'';
};
includes = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
List of files to include in configuration
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = "Additional entries to put verbatim into worker section of rspamd config file.";
};
};
config =
mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy")
{
type = mkDefault name;
includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
bindSockets =
let
unixSocket = name: {
mode = "0660";
socket = "/run/rspamd/${name}.sock";
owner = cfg.user;
group = cfg.group;
};
in
mkDefault (
if name == "normal" then
[ (unixSocket "rspamd") ]
else if name == "controller" then
[ "localhost:11334" ]
else if name == "rspamd_proxy" then
[ (unixSocket "proxy") ]
else
[ ]
);
};
};
isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
mkBindSockets =
enabled: socks:
concatStringsSep "\n " (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
rspamdConfFile = pkgs.writeText "rspamd.conf" ''
.include "$CONFDIR/common.conf"
options {
pidfile = "$RUNDIR/rspamd.pid";
.include "$CONFDIR/options.inc"
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
.include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
}
logging {
type = "syslog";
.include "$CONFDIR/logging.inc"
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
.include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
}
${concatStringsSep "\n" (
mapAttrsToList (
name: value:
let
includeName = if name == "rspamd_proxy" then "proxy" else name;
tryOverride = boolToString (value.extraConfig == "");
in
''
worker "${value.type}" {
type = "${value.type}";
${optionalString (value.enable != null)
"enabled = ${if value.enable != false then "yes" else "no"};"
}
${mkBindSockets value.enable value.bindSockets}
${optionalString (value.count != null) "count = ${toString value.count};"}
${concatStringsSep "\n " (map (each: ".include \"${each}\"") value.includes)}
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
.include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
}
''
) cfg.workers
)}
${optionalString (cfg.extraConfig != "") ''
.include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
''}
'';
filterFiles = files: filterAttrs (n: v: v.enable) files;
rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
(mapAttrsToList (name: file: {
name = "local.d/${name}";
path = file.source;
}) (filterFiles cfg.locals))
++ (mapAttrsToList (name: file: {
name = "override.d/${name}";
path = file.source;
}) (filterFiles cfg.overrides))
++ (optional (cfg.localLuaRules != null) {
name = "rspamd.local.lua";
path = cfg.localLuaRules;
})
++ [
{
name = "rspamd.conf";
path = rspamdConfFile;
}
]
);
configFileModule =
prefix:
{ name, config, ... }:
{
options = {
enable = mkOption {
type = types.bool;
default = true;
description = ''
Whether this file ${prefix} should be generated. This
option allows specific ${prefix} files to be disabled.
'';
};
text = mkOption {
default = null;
type = types.nullOr types.lines;
description = "Text of the file.";
};
source = mkOption {
type = types.path;
description = "Path of the source file.";
};
};
config = {
source = mkIf (config.text != null) (
let
name' = "rspamd-${prefix}-" + baseNameOf name;
in
mkDefault (pkgs.writeText name' config.text)
);
};
};
configOverrides =
(mapAttrs' (
n: v:
nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
text = v.extraConfig;
}
) (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
// (lib.optionalAttrs (cfg.extraConfig != "") {
"extra-config.inc".text = cfg.extraConfig;
});
2016-01-12 11:06:46 +02:00
in
{
###### interface
options = {
services.rspamd = {
enable = mkEnableOption "rspamd, the Rapid spam filtering system";
2016-01-12 11:06:46 +02:00
debug = mkOption {
type = types.bool;
2016-01-12 11:06:46 +02:00
default = false;
description = "Whether to run the rspamd daemon in debug mode.";
};
locals = mkOption {
type = with types; attrsOf (submodule (configFileModule "locals"));
default = { };
description = ''
Local configuration files, written into {file}`/etc/rspamd/local.d/{name}`.
'';
example = literalExpression ''
{ "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
"arc.conf".text = "allow_envfrom_empty = true;";
}
'';
};
overrides = mkOption {
type = with types; attrsOf (submodule (configFileModule "overrides"));
default = { };
description = ''
Overridden configuration files, written into {file}`/etc/rspamd/override.d/{name}`.
'';
example = literalExpression ''
{ "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
"arc.conf".text = "allow_envfrom_empty = true;";
}
'';
};
localLuaRules = mkOption {
default = null;
type = types.nullOr types.path;
description = ''
Path of file to link to {file}`/etc/rspamd/rspamd.local.lua` for local
rules written in Lua
'';
};
workers = mkOption {
type = with types; attrsOf (submodule workerOpts);
description = ''
Attribute set of workers to start.
'';
default = {
normal = { };
controller = { };
};
example = literalExpression ''
{
normal = {
includes = [ "$CONFDIR/worker-normal.inc" ];
bindSockets = [{
socket = "/run/rspamd/rspamd.sock";
mode = "0660";
owner = "''${config.${opt.user}}";
group = "''${config.${opt.group}}";
}];
};
controller = {
includes = [ "$CONFDIR/worker-controller.inc" ];
bindSockets = [ "[::1]:11334" ];
};
}
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra configuration to add at the end of the rspamd configuration
file.
'';
};
2016-01-12 11:06:46 +02:00
user = mkOption {
type = types.str;
2016-01-12 11:06:46 +02:00
default = "rspamd";
description = ''
User to use when no root privileges are required.
'';
};
2016-01-12 11:06:46 +02:00
group = mkOption {
type = types.str;
2016-01-12 11:06:46 +02:00
default = "rspamd";
description = ''
Group to use when no root privileges are required.
'';
};
postfix = {
enable = mkOption {
type = types.bool;
default = false;
description = "Add rspamd milter to postfix main.conf";
};
config = mkOption {
type =
with types;
attrsOf (oneOf [
bool
str
(listOf str)
]);
description = ''
Addon to postfix configuration
'';
default = {
smtpd_milters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
non_smtpd_milters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
};
};
};
2016-01-12 11:06:46 +02:00
};
};
###### implementation
config = mkIf cfg.enable {
services.rspamd.overrides = configOverrides;
services.rspamd.workers = mkIf cfg.postfix.enable {
controller = { };
rspamd_proxy = {
bindSockets = [
{
mode = "0660";
socket = "/run/rspamd/rspamd-milter.sock";
owner = cfg.user;
group = postfixCfg.group;
}
];
extraConfig = ''
upstream "local" {
default = yes; # Self-scan upstreams are always default
self_scan = yes; # Enable self-scan
}
'';
};
};
services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config;
2016-01-12 11:06:46 +02:00
systemd.services.postfix = mkIf cfg.postfix.enable {
serviceConfig.SupplementaryGroups = [ postfixCfg.group ];
};
2020-11-29 12:51:53 +01:00
2016-01-12 11:06:46 +02:00
# Allow users to run 'rspamc' and 'rspamadm'.
environment.systemPackages = [ pkgs.rspamd ];
users.users.${cfg.user} = {
2016-01-12 11:06:46 +02:00
description = "rspamd daemon";
uid = config.ids.uids.rspamd;
group = cfg.group;
};
users.groups.${cfg.group} = {
gid = config.ids.gids.rspamd;
2016-01-12 11:06:46 +02:00
};
2019-08-13 21:52:01 +00:00
environment.etc.rspamd.source = rspamdDir;
2016-01-12 11:06:46 +02:00
systemd.services.rspamd = {
description = "Rspamd Service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
restartTriggers = [ rspamdDir ];
2016-01-12 11:06:46 +02:00
serviceConfig = {
ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} -c /etc/rspamd/rspamd.conf -f";
2016-01-12 11:06:46 +02:00
Restart = "always";
User = "${cfg.user}";
Group = "${cfg.group}";
2020-11-29 12:51:53 +01:00
SupplementaryGroups = mkIf cfg.postfix.enable [ postfixCfg.group ];
RuntimeDirectory = "rspamd";
RuntimeDirectoryMode = "0755";
StateDirectory = "rspamd";
StateDirectoryMode = "0700";
2020-07-18 12:25:07 +02:00
AmbientCapabilities = [ ];
CapabilityBoundingSet = "";
DevicePolicy = "closed";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
2020-11-29 12:51:53 +01:00
# we need to chown socket to rspamd-milter
PrivateUsers = !cfg.postfix.enable;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
2020-07-18 12:25:07 +02:00
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "0077";
2016-01-12 11:06:46 +02:00
};
};
};
imports = [
(mkRemovedOptionModule [
"services"
"rspamd"
"socketActivation"
2020-11-22 17:23:53 +10:00
] "Socket activation never worked correctly and could at this time not be fixed and so was removed")
(mkRenamedOptionModule
[ "services" "rspamd" "bindSocket" ]
[ "services" "rspamd" "workers" "normal" "bindSockets" ]
)
(mkRenamedOptionModule
[ "services" "rspamd" "bindUISocket" ]
[ "services" "rspamd" "workers" "controller" "bindSockets" ]
)
(mkRemovedOptionModule [
"services"
"rmilter"
] "Use services.rspamd.* instead to set up milter service")
];
2016-01-12 11:06:46 +02:00
}