mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-27 16:15:05 +00:00
283 lines
8.2 KiB
Nix
283 lines
8.2 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.suricata;
|
|
pkg = cfg.package;
|
|
yaml = pkgs.formats.yaml { };
|
|
inherit (lib)
|
|
mkEnableOption
|
|
mkPackageOption
|
|
mkOption
|
|
types
|
|
literalExpression
|
|
filterAttrsRecursive
|
|
concatStringsSep
|
|
strings
|
|
lists
|
|
mkIf
|
|
;
|
|
in
|
|
{
|
|
meta.maintainers = with lib.maintainers; [ felbinger ];
|
|
|
|
options.services.suricata = {
|
|
enable = mkEnableOption "Suricata";
|
|
|
|
package = mkPackageOption pkgs "suricata" { };
|
|
|
|
configFile = mkOption {
|
|
type = types.path;
|
|
visible = false;
|
|
default = pkgs.writeTextFile {
|
|
name = "suricata.yaml";
|
|
text = ''
|
|
%YAML 1.1
|
|
---
|
|
${builtins.readFile (
|
|
yaml.generate "suricata-settings-raw.yaml" (
|
|
filterAttrsRecursive (name: value: value != null) cfg.settings
|
|
)
|
|
)}
|
|
'';
|
|
};
|
|
description = ''
|
|
Configuration file for suricata.
|
|
|
|
It is not usual to override the default values; it is recommended to use `settings`.
|
|
If you want to include extra configuration to the file, use the `settings.includes`.
|
|
'';
|
|
};
|
|
|
|
settings = mkOption {
|
|
type = types.submodule (import ./settings.nix { inherit config lib yaml; });
|
|
example = literalExpression ''
|
|
vars.address-groups.HOME_NET = "192.168.178.0/24";
|
|
outputs = [
|
|
{
|
|
fast = {
|
|
enabled = true;
|
|
filename = "fast.log";
|
|
append = "yes";
|
|
};
|
|
}
|
|
{
|
|
eve-log = {
|
|
enabled = true;
|
|
filetype = "regular";
|
|
filename = "eve.json";
|
|
community-id = true;
|
|
types = [
|
|
{
|
|
alert.tagged-packets = "yes";
|
|
}
|
|
];
|
|
};
|
|
}
|
|
];
|
|
af-packet = [
|
|
{
|
|
interface = "eth0";
|
|
cluster-id = "99";
|
|
cluster-type = "cluster_flow";
|
|
defrag = "yes";
|
|
}
|
|
{
|
|
interface = "default";
|
|
}
|
|
];
|
|
af-xdp = [
|
|
{
|
|
interface = "eth1";
|
|
}
|
|
];
|
|
dpdk.interfaces = [
|
|
{
|
|
interface = "eth2";
|
|
}
|
|
];
|
|
pcap = [
|
|
{
|
|
interface = "eth3";
|
|
}
|
|
];
|
|
app-layer.protocols = {
|
|
telnet.enabled = "yes";
|
|
dnp3.enabled = "yes";
|
|
modbus.enabled = "yes";
|
|
};
|
|
'';
|
|
description = "Suricata settings";
|
|
};
|
|
|
|
enabledSources = mkOption {
|
|
type = types.listOf types.str;
|
|
# see: nix-shell -p suricata python3Packages.pyyaml --command 'suricata-update list-sources'
|
|
default = [
|
|
"et/open"
|
|
"etnetera/aggressive"
|
|
"stamus/lateral"
|
|
"oisf/trafficid"
|
|
"tgreen/hunting"
|
|
"sslbl/ja3-fingerprints"
|
|
"sslbl/ssl-fp-blacklist"
|
|
"malsilo/win-malware"
|
|
"pawpatrules"
|
|
];
|
|
description = ''
|
|
List of sources that should be enabled.
|
|
Currently sources which require a secret-code are not supported.
|
|
'';
|
|
};
|
|
|
|
disabledRules = mkOption {
|
|
type = types.listOf types.str;
|
|
# protocol dnp3 seams to be disabled, which causes the signature evaluation to fail, so we disable the
|
|
# dnp3 rules, see https://github.com/OISF/suricata/blob/master/rules/dnp3-events.rules for more details
|
|
default = [
|
|
"2270000"
|
|
"2270001"
|
|
"2270002"
|
|
"2270003"
|
|
"2270004"
|
|
];
|
|
description = ''
|
|
List of rules that should be disabled.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config =
|
|
let
|
|
captureInterfaces =
|
|
let
|
|
inherit (lists) unique optionals;
|
|
in
|
|
unique (
|
|
map (e: e.interface) (
|
|
(optionals (cfg.settings.af-packet != null) cfg.settings.af-packet)
|
|
++ (optionals (cfg.settings.af-xdp != null) cfg.settings.af-xdp)
|
|
++ (optionals (
|
|
cfg.settings.dpdk != null && cfg.settings.dpdk.interfaces != null
|
|
) cfg.settings.dpdk.interfaces)
|
|
++ (optionals (cfg.settings.pcap != null) cfg.settings.pcap)
|
|
)
|
|
);
|
|
in
|
|
mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = (builtins.length captureInterfaces) > 0;
|
|
message = ''
|
|
At least one capture interface must be configured:
|
|
- `services.suricata.settings.af-packet`
|
|
- `services.suricata.settings.af-xdp`
|
|
- `services.suricata.settings.dpdk.interfaces`
|
|
- `services.suricata.settings.pcap`
|
|
'';
|
|
}
|
|
];
|
|
|
|
boot.kernelModules = mkIf (cfg.settings.af-packet != null) [ "af_packet" ];
|
|
|
|
users = {
|
|
groups.${cfg.settings.run-as.group} = { };
|
|
users.${cfg.settings.run-as.user} = {
|
|
group = cfg.settings.run-as.group;
|
|
isSystemUser = true;
|
|
};
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.settings."default-log-dir"} 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}"
|
|
"d /var/lib/suricata 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}"
|
|
"d ${cfg.settings."default-rule-path"} 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}"
|
|
];
|
|
|
|
systemd.services = {
|
|
suricata-update = {
|
|
description = "Update Suricata Rules";
|
|
wantedBy = [ "multi-user.target" ];
|
|
wants = [ "network-online.target" ];
|
|
after = [ "network-online.target" ];
|
|
|
|
script =
|
|
let
|
|
python = pkgs.python3.withPackages (ps: with ps; [ pyyaml ]);
|
|
enabledSourcesCmds = map (
|
|
src: "${python.interpreter} ${pkg}/bin/suricata-update enable-source ${src}"
|
|
) cfg.enabledSources;
|
|
in
|
|
''
|
|
${concatStringsSep "\n" enabledSourcesCmds}
|
|
${python.interpreter} ${pkg}/bin/suricata-update update-sources
|
|
${python.interpreter} ${pkg}/bin/suricata-update update --suricata-conf ${cfg.configFile} --no-test \
|
|
--disable-conf ${pkgs.writeText "suricata-disable-conf" "${concatStringsSep "\n" cfg.disabledRules}"}
|
|
'';
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
PrivateIPC = true;
|
|
|
|
DynamicUser = true;
|
|
User = cfg.settings.run-as.user;
|
|
Group = cfg.settings.run-as.group;
|
|
|
|
ReadOnlyPaths = cfg.configFile;
|
|
ReadWritePaths = [
|
|
"/var/lib/suricata"
|
|
cfg.settings."default-rule-path"
|
|
];
|
|
};
|
|
};
|
|
suricata = {
|
|
description = "Suricata";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "suricata-update.service" ];
|
|
serviceConfig =
|
|
let
|
|
interfaceOptions = strings.concatMapStrings (interface: " -i ${interface}") captureInterfaces;
|
|
in
|
|
{
|
|
ExecStartPre = "!${pkg}/bin/suricata -c ${cfg.configFile} -T";
|
|
ExecStart = "!${pkg}/bin/suricata -c ${cfg.configFile}${interfaceOptions}";
|
|
Restart = "on-failure";
|
|
|
|
User = cfg.settings.run-as.user;
|
|
Group = cfg.settings.run-as.group;
|
|
|
|
NoNewPrivileges = true;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
PrivateIPC = true;
|
|
ProtectSystem = "strict";
|
|
DevicePolicy = "closed";
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = true;
|
|
ProtectHostname = true;
|
|
ProtectProc = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectControlGroups = true;
|
|
ProcSubset = "pid";
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
SystemCallArchitectures = "native";
|
|
RemoveIPC = true;
|
|
|
|
ReadOnlyPaths = cfg.configFile;
|
|
ReadWritePaths = cfg.settings."default-log-dir";
|
|
RuntimeDirectory = "suricata";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|