mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-03-07 23:04:00 +01:00
308 lines
9.6 KiB
Nix
308 lines
9.6 KiB
Nix
{
|
|
lib,
|
|
pkgs,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
settingsFormat = pkgs.formats.yaml { };
|
|
|
|
cfg = config.services.reaction;
|
|
|
|
inherit (lib)
|
|
concatMapStringsSep
|
|
filterAttrs
|
|
getExe
|
|
mkDefault
|
|
mkEnableOption
|
|
mkIf
|
|
mkOption
|
|
mkPackageOption
|
|
mapAttrs
|
|
optional
|
|
optionals
|
|
optionalString
|
|
types
|
|
;
|
|
in
|
|
{
|
|
options.services.reaction = {
|
|
enable = mkEnableOption "enable reaction";
|
|
package = mkPackageOption pkgs "reaction" { };
|
|
|
|
settings = mkOption {
|
|
description = ''
|
|
Configuration for reaction. See the [wiki](https://framagit.org/ppom/reaction-wiki).
|
|
|
|
The settings are written as a YAML file.
|
|
|
|
Can be used in combination with `settingsFiles` option, both will be present in the configuration directory.
|
|
'';
|
|
default = { };
|
|
type = types.submodule {
|
|
freeformType = settingsFormat.type;
|
|
options = {
|
|
plugins = mkOption {
|
|
description = ''
|
|
Nixpkgs provides a `reaction-plugins` package set which includes both offical and community plugins for reaction.
|
|
|
|
To use the plugins in your module configuration, in `settings.plugins` you can use for e.g. `''${lib.getExe reaction-plugins.reaction-plugin-ipset}`
|
|
See https://reaction.ppom.me/plugins/ to configure plugins.
|
|
'';
|
|
default = { };
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
{ name, ... }:
|
|
{
|
|
options = {
|
|
enable = mkOption {
|
|
description = "enable reaction-plugin-${name}";
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
path = mkOption {
|
|
description = "path to the plugin binary";
|
|
type = types.str;
|
|
default = "${cfg.package.plugins."reaction-plugin-${name}"}/bin/reaction-plugin-${name}";
|
|
defaultText = lib.literalExpression ''''${cfg.package.plugins."reaction-plugin-${name}"}/bin/reaction-plugin-${name}'';
|
|
};
|
|
check_root = mkOption {
|
|
description = "Whether reaction must check that the executable is owned by root";
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
systemd = mkOption {
|
|
description = "Whether reaction must isolate the plugin using systemd's run0";
|
|
type = types.bool;
|
|
default = cfg.runAsRoot;
|
|
defaultText = "config.services.reaction.runAsRoot";
|
|
};
|
|
systemd_options = mkOption {
|
|
description = ''
|
|
A key-value map of systemd options.
|
|
Keys must be strings and values must be string arrays.
|
|
|
|
See `man systemd.directives` for all supported options, and particularly options in `man systemd.exec`
|
|
'';
|
|
type = types.attrsOf (types.listOf types.str);
|
|
default = { };
|
|
};
|
|
};
|
|
}
|
|
)
|
|
);
|
|
# Filter plugins which are disabled
|
|
apply =
|
|
self:
|
|
lib.pipe self [
|
|
(filterAttrs (name: p: p.enable))
|
|
(mapAttrs (name: p: removeAttrs p [ "enable" ]))
|
|
];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
settingsFiles = mkOption {
|
|
description = ''
|
|
Configuration for reaction, see the [wiki](https://framagit.org/ppom/reaction-wiki).
|
|
|
|
reaction supports JSON, YAML and JSONnet. For those who prefer to take advantage of JSONnet rather than Nix.
|
|
|
|
Can be used in combination with `settings` option, both will be present in the configuration directory.
|
|
'';
|
|
default = [ ];
|
|
type = types.listOf types.path;
|
|
};
|
|
|
|
loglevel = mkOption {
|
|
description = ''
|
|
reaction's loglevel. One of DEBUG, INFO, WARN, ERROR.
|
|
'';
|
|
default = null;
|
|
type = types.nullOr (
|
|
types.enum [
|
|
"DEBUG"
|
|
"INFO"
|
|
"WARN"
|
|
"ERROR"
|
|
]
|
|
);
|
|
};
|
|
|
|
stopForFirewall = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to stop reaction when reloading the firewall.
|
|
|
|
The presence of a reaction chain in the INPUT table may cause the firewall
|
|
reload to fail.
|
|
One can alternatively cherry-pick the right iptables commands to execute before and after the firewall
|
|
```nix
|
|
{
|
|
systemd.services.firewall.serviceConfig = {
|
|
ExecStopPre = [ "''${pkgs.iptables}/bin/iptables -w -D INPUT -p all -j reaction" ];
|
|
ExecStartPost = [ "''${pkgs.iptables}/bin/iptables -w -I INPUT -p all -j reaction" ];
|
|
};
|
|
}
|
|
```
|
|
'';
|
|
};
|
|
|
|
checkConfig = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Check the syntax of the configuration files at build time";
|
|
};
|
|
|
|
runAsRoot = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to run reaction as root.
|
|
Defaults to false, where an unprivileged reaction user is created.
|
|
|
|
Be sure to give it sufficient permissions.
|
|
Example config permitting `iptables` and `journalctl` use
|
|
|
|
```nix
|
|
{
|
|
# allows reading journal logs of processess
|
|
users.users.reaction.extraGroups = [ "systemd-journal" ];
|
|
|
|
# allows modifying ip firewall rules
|
|
systemd.services.reaction.unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
|
systemd.services.reaction.serviceConfig = {
|
|
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
|
|
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
|
|
};
|
|
|
|
# optional, if more control over ssh logs is needed
|
|
services.openssh.settings.LogLevel = lib.mkDefault "VERBOSE";
|
|
}
|
|
```
|
|
|
|
```nix
|
|
# core ipset plugin requires these if running as non-root
|
|
systemd.services.reaction.serviceConfig = {
|
|
CapabilityBoundingSet = [
|
|
"CAP_NET_ADMIN"
|
|
"CAP_NET_RAW"
|
|
"CAP_DAC_READ_SEARCH" # for journalctl
|
|
];
|
|
AmbientCapabilities = [
|
|
"CAP_NET_ADMIN"
|
|
"CAP_NET_RAW"
|
|
"CAP_DAC_READ_SEARCH"
|
|
];
|
|
};
|
|
```
|
|
'';
|
|
};
|
|
};
|
|
|
|
config =
|
|
let
|
|
generatedSettings = settingsFormat.generate "reaction.yml" cfg.settings;
|
|
settingsDir = pkgs.runCommand "reaction-settings-dir" { } ''
|
|
mkdir -p $out
|
|
${concatMapStringsSep "\n" (file: ''
|
|
filename=$(basename "${file}")
|
|
ln -s "${file}" "$out/$filename"
|
|
'') cfg.settingsFiles}
|
|
ln -s ${generatedSettings} $out/reaction.yml
|
|
'';
|
|
in
|
|
mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = cfg.settings != { } || (builtins.length cfg.settingsFiles) != 0;
|
|
message = "You must specify settings and/or settingsFile options";
|
|
}
|
|
];
|
|
|
|
users = mkIf (!cfg.runAsRoot) {
|
|
users.reaction = {
|
|
isSystemUser = true;
|
|
group = "reaction";
|
|
};
|
|
groups.reaction = { };
|
|
};
|
|
|
|
system.checks =
|
|
optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform)
|
|
(
|
|
pkgs.runCommand "reaction-config-validation" { } ''
|
|
${getExe cfg.package} test-config -c ${settingsDir} >/dev/null
|
|
echo "reaction config ${settingsDir} is valid"
|
|
touch $out
|
|
''
|
|
);
|
|
|
|
systemd.services.reaction = {
|
|
description = "A daemon that scans program outputs for repeated patterns, and takes action.";
|
|
documentation = [ "https://reaction.ppom.me" ];
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
partOf = optionals cfg.stopForFirewall [ "firewall.service" ];
|
|
path = [ pkgs.iptables ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
KillMode = "mixed"; # for plugins
|
|
User = if (!cfg.runAsRoot) then "reaction" else "root";
|
|
ExecStart = ''
|
|
${getExe cfg.package} start -c ${settingsDir}${
|
|
optionalString (cfg.loglevel != null) " -l ${cfg.loglevel}"
|
|
}
|
|
'';
|
|
|
|
NoNewPrivileges = true;
|
|
|
|
RuntimeDirectory = "reaction";
|
|
RuntimeDirectoryMode = "0750";
|
|
WorkingDirectory = "%S/reaction";
|
|
StateDirectory = "reaction";
|
|
StateDirectoryMode = "0750";
|
|
LogsDirectory = "reaction";
|
|
LogsDirectoryMode = "0750";
|
|
UMask = 0077;
|
|
|
|
RemoveIPC = true;
|
|
PrivateTmp = true;
|
|
ProtectHome = true;
|
|
ProtectClock = true;
|
|
PrivateDevices = true;
|
|
ProtectHostname = true;
|
|
ProtectSystem = "strict";
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
ProtectKernelLogs = true;
|
|
};
|
|
};
|
|
|
|
# pre-configure official plugins
|
|
services.reaction.settings.plugins = {
|
|
ipset = {
|
|
enable = mkDefault true;
|
|
systemd_options = {
|
|
CapabilityBoundingSet = [
|
|
"~CAP_NET_ADMIN"
|
|
"~CAP_PERFMON"
|
|
];
|
|
};
|
|
};
|
|
virtual.enable = mkDefault true;
|
|
};
|
|
|
|
environment.systemPackages = [ cfg.package ];
|
|
};
|
|
|
|
meta.maintainers =
|
|
with lib.maintainers;
|
|
[
|
|
ppom
|
|
]
|
|
++ lib.teams.ngi.members;
|
|
}
|