mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-22 06:53:01 +00:00
nixos/suricata: init module
This commit is contained in:
parent
53a0f1bc5c
commit
924ee0c2bc
@ -132,6 +132,8 @@
|
||||
|
||||
- [Gotenberg](https://gotenberg.dev), an API server for converting files to PDFs that can be used alongside Paperless-ngx. Available as [services.gotenberg](options.html#opt-services.gotenberg).
|
||||
|
||||
- [Suricata](https://suricata.io/), a free and open source, mature, fast and robust network threat detection engine. Available as [services.suricata](options.html#opt-services.suricata).
|
||||
|
||||
- [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld).
|
||||
|
||||
- [MenhirLib](https://gitlab.inria.fr/fpottier/menhir/-/tree/master/coq-menhirlib) A support library for verified Coq parsers produced by Menhir.
|
||||
|
282
nixos/modules/services/networking/suricata/default.nix
Normal file
282
nixos/modules/services/networking/suricata/default.nix
Normal file
@ -0,0 +1,282 @@
|
||||
{
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
625
nixos/modules/services/networking/suricata/settings.nix
Normal file
625
nixos/modules/services/networking/suricata/settings.nix
Normal file
@ -0,0 +1,625 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
yaml,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.suricata;
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkOption
|
||||
types
|
||||
literalExpression
|
||||
;
|
||||
mkDisableOption =
|
||||
name:
|
||||
mkEnableOption name
|
||||
// {
|
||||
default = true;
|
||||
example = false;
|
||||
};
|
||||
in
|
||||
{
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
vars = mkOption {
|
||||
type = types.nullOr (
|
||||
types.submodule {
|
||||
options = {
|
||||
address-groups = mkOption {
|
||||
type = (
|
||||
types.submodule {
|
||||
options = {
|
||||
HOME_NET = mkOption { default = "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"; };
|
||||
EXTERNAL_NET = mkOption { default = "!$HOME_NET"; };
|
||||
HTTP_SERVERS = mkOption { default = "$HOME_NET"; };
|
||||
SMTP_SERVERS = mkOption { default = "$HOME_NET"; };
|
||||
SQL_SERVERS = mkOption { default = "$HOME_NET"; };
|
||||
DNS_SERVERS = mkOption { default = "$HOME_NET"; };
|
||||
TELNET_SERVERS = mkOption { default = "$HOME_NET"; };
|
||||
AIM_SERVERS = mkOption { default = "$EXTERNAL_NET"; };
|
||||
DC_SERVERS = mkOption { default = "$HOME_NET"; };
|
||||
DNP3_SERVER = mkOption { default = "$HOME_NET"; };
|
||||
DNP3_CLIENT = mkOption { default = "$HOME_NET"; };
|
||||
MODBUS_CLIENT = mkOption { default = "$HOME_NET"; };
|
||||
MODBUS_SERVER = mkOption { default = "$HOME_NET"; };
|
||||
ENIP_CLIENT = mkOption { default = "$HOME_NET"; };
|
||||
ENIP_SERVER = mkOption { default = "$HOME_NET"; };
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
example = {
|
||||
HOME_NET = "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]";
|
||||
EXTERNAL_NET = "!$HOME_NET";
|
||||
HTTP_SERVERS = "$HOME_NET";
|
||||
SMTP_SERVERS = "$HOME_NET";
|
||||
SQL_SERVERS = "$HOME_NET";
|
||||
DNS_SERVERS = "$HOME_NET";
|
||||
TELNET_SERVERS = "$HOME_NET";
|
||||
AIM_SERVERS = "$EXTERNAL_NET";
|
||||
DC_SERVERS = "$HOME_NET";
|
||||
DNP3_SERVER = "$HOME_NET";
|
||||
DNP3_CLIENT = "$HOME_NET";
|
||||
MODBUS_CLIENT = "$HOME_NET";
|
||||
MODBUS_SERVER = "$HOME_NET";
|
||||
ENIP_CLIENT = "$HOME_NET";
|
||||
ENIP_SERVER = "$HOME_NET";
|
||||
};
|
||||
description = ''
|
||||
The address group variables for suricata, if not defined the
|
||||
default value of suricata (see example) will be used.
|
||||
Your settings will extend the predefined values in example.
|
||||
'';
|
||||
};
|
||||
|
||||
port-groups = mkOption {
|
||||
type = with types; nullOr (attrsOf str);
|
||||
default = {
|
||||
HTTP_PORTS = "80";
|
||||
SHELLCODE_PORTS = "!80";
|
||||
ORACLE_PORTS = "1521";
|
||||
SSH_PORTS = "22";
|
||||
DNP3_PORTS = "20000";
|
||||
MODBUS_PORTS = "502";
|
||||
FILE_DATA_PORTS = "[$HTTP_PORTS,110,143]";
|
||||
FTP_PORTS = "21";
|
||||
GENEVE_PORTS = "6081";
|
||||
VXLAN_PORTS = "4789";
|
||||
TEREDO_PORTS = "3544";
|
||||
};
|
||||
description = ''
|
||||
The port group variables for suricata.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { }; # add default values to config
|
||||
};
|
||||
|
||||
stats = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "suricata global stats";
|
||||
|
||||
interval = mkOption {
|
||||
type = types.str;
|
||||
default = "8";
|
||||
description = ''
|
||||
The interval field (in seconds) controls the interval at
|
||||
which stats are updated in the log.
|
||||
'';
|
||||
};
|
||||
|
||||
decoder-events = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Add decode events to stats
|
||||
'';
|
||||
};
|
||||
|
||||
decoder-events-prefix = mkOption {
|
||||
type = types.str;
|
||||
default = "decoder.event";
|
||||
description = ''
|
||||
Decoder event prefix in stats. Has been 'decoder' before, but that leads
|
||||
to missing events in the eve.stats records.
|
||||
'';
|
||||
};
|
||||
|
||||
stream-events = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Add stream events as stats.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null; # do not add to config unless specified
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = with types; nullOr (listOf path);
|
||||
default = null;
|
||||
description = ''
|
||||
Plugins -- Experimental -- specify the filename for each plugin shared object
|
||||
'';
|
||||
};
|
||||
|
||||
outputs = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
listOf (
|
||||
attrsOf (submodule {
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
enabled = mkEnableOption "<NAME>";
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
[
|
||||
{
|
||||
fast = {
|
||||
enabled = "yes";
|
||||
filename = "fast.log";
|
||||
append = "yes";
|
||||
};
|
||||
}
|
||||
{
|
||||
eve-log = {
|
||||
enabled = "yes";
|
||||
filetype = "regular";
|
||||
filename = "eve.json";
|
||||
community-id = true;
|
||||
types = [
|
||||
{
|
||||
alert.tagged-packets = "yes";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
];
|
||||
'';
|
||||
description = ''
|
||||
Configure the type of alert (and other) logging you would like.
|
||||
|
||||
Valid values for <NAME> are e. g. `fast`, `eve-log`, `syslog`, `file-store`, ...
|
||||
- `fast`: a line based alerts log similar to Snort's fast.log
|
||||
- `eve-log`: Extensible Event Format (nicknamed EVE) event log in JSON format
|
||||
|
||||
For more details regarding the configuration, checkout the shipped suricata.yaml
|
||||
```shell
|
||||
nix-shell -p suricata yq coreutils-full --command 'yq < $(dirname $(which suricata))/../etc/suricata/suricata.yaml'
|
||||
```
|
||||
and the [suricata documentation](https://docs.suricata.io/en/latest/output/index.html).
|
||||
'';
|
||||
};
|
||||
|
||||
"default-log-dir" = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/log/suricata";
|
||||
description = ''
|
||||
The default logging directory. Any log or output file will be placed here if it's
|
||||
not specified with a full path name. This can be overridden with the -l command
|
||||
line parameter.
|
||||
'';
|
||||
};
|
||||
|
||||
logging = {
|
||||
"default-log-level" = mkOption {
|
||||
type = types.enum [
|
||||
"error"
|
||||
"warning"
|
||||
"notice"
|
||||
"info"
|
||||
"perf"
|
||||
"config"
|
||||
"debug"
|
||||
];
|
||||
default = "notice";
|
||||
description = ''
|
||||
The default log level: can be overridden in an output section.
|
||||
Note that debug level logging will only be emitted if Suricata was
|
||||
compiled with the --enable-debug configure option.
|
||||
'';
|
||||
};
|
||||
|
||||
"default-log-format" = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The default output format. Optional parameter, should default to
|
||||
something reasonable if not provided. Can be overridden in an
|
||||
output section. You can leave this out to get the default.
|
||||
'';
|
||||
};
|
||||
|
||||
"default-output-filter" = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
A regex to filter output. Can be overridden in an output section.
|
||||
Defaults to empty (no filter).
|
||||
'';
|
||||
};
|
||||
|
||||
"stacktrace-on-signal" = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Requires libunwind to be available when Suricata is configured and built.
|
||||
If a signal unexpectedly terminates Suricata, displays a brief diagnostic
|
||||
message with the offending stacktrace if enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
outputs = {
|
||||
console = {
|
||||
enable = mkDisableOption "logging to console";
|
||||
};
|
||||
file = {
|
||||
enable = mkDisableOption "logging to file";
|
||||
|
||||
level = mkOption {
|
||||
type = types.enum [
|
||||
"error"
|
||||
"warning"
|
||||
"notice"
|
||||
"info"
|
||||
"perf"
|
||||
"config"
|
||||
"debug"
|
||||
];
|
||||
default = "info";
|
||||
description = ''
|
||||
Loglevel for logs written to the logfile
|
||||
'';
|
||||
};
|
||||
|
||||
filename = mkOption {
|
||||
type = types.str;
|
||||
default = "suricata.log";
|
||||
description = ''
|
||||
Filename of the logfile
|
||||
'';
|
||||
};
|
||||
|
||||
format = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Logformat for logs written to the logfile
|
||||
'';
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Type of logfile
|
||||
'';
|
||||
};
|
||||
};
|
||||
syslog = {
|
||||
enable = mkEnableOption "logging to syslog";
|
||||
|
||||
facility = mkOption {
|
||||
type = types.str;
|
||||
default = "local5";
|
||||
description = ''
|
||||
Facility to log to
|
||||
'';
|
||||
};
|
||||
|
||||
format = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Logformat for logs send to syslog
|
||||
'';
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Type of logs send to syslog
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
"af-packet" = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
listOf (submodule {
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
Linux high speed capture support
|
||||
'';
|
||||
};
|
||||
|
||||
"af-xdp" = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
listOf (submodule {
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
Linux high speed af-xdp capture support, see
|
||||
[docs/capture-hardware/af-xdp](https://docs.suricata.io/en/suricata-7.0.3/capture-hardware/af-xdp.html)
|
||||
'';
|
||||
};
|
||||
|
||||
"dpdk" = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (submodule {
|
||||
options = {
|
||||
eal-params.proc-type = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
};
|
||||
interfaces = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
listOf (submodule {
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null;
|
||||
description = ''
|
||||
DPDK capture support, see
|
||||
[docs/capture-hardware/dpdk](https://docs.suricata.io/en/suricata-7.0.3/capture-hardware/dpdk.html)
|
||||
'';
|
||||
};
|
||||
|
||||
"pcap" = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
listOf (submodule {
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
Cross platform libpcap capture support
|
||||
'';
|
||||
};
|
||||
|
||||
"pcap-file".checksum-checks = mkOption {
|
||||
type = types.enum [
|
||||
"yes"
|
||||
"no"
|
||||
"auto"
|
||||
];
|
||||
default = "auto";
|
||||
description = ''
|
||||
Possible values are:
|
||||
- yes: checksum validation is forced
|
||||
- no: checksum validation is disabled
|
||||
- auto: Suricata uses a statistical approach to detect when
|
||||
checksum off-loading is used. (default)
|
||||
Warning: 'checksum-validation' must be set to yes to have checksum tested
|
||||
'';
|
||||
};
|
||||
|
||||
"app-layer" = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (submodule {
|
||||
options = {
|
||||
"error-policy" = mkOption {
|
||||
type = types.enum [
|
||||
"drop-flow"
|
||||
"pass-flow"
|
||||
"bypass"
|
||||
"drop-packet"
|
||||
"pass-packet"
|
||||
"reject"
|
||||
"ignore"
|
||||
];
|
||||
default = "ignore";
|
||||
description = ''
|
||||
The error-policy setting applies to all app-layer parsers. Values can be
|
||||
"drop-flow", "pass-flow", "bypass", "drop-packet", "pass-packet", "reject" or
|
||||
"ignore" (the default).
|
||||
'';
|
||||
};
|
||||
protocols = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
attrsOf (submodule {
|
||||
freeformType = yaml.type;
|
||||
options = {
|
||||
enabled = mkOption {
|
||||
type = types.enum [
|
||||
"yes"
|
||||
"no"
|
||||
"detection-only"
|
||||
];
|
||||
default = "no";
|
||||
description = ''
|
||||
The option "enabled" takes 3 values - "yes", "no", "detection-only".
|
||||
"yes" enables both detection and the parser, "no" disables both, and
|
||||
"detection-only" enables protocol detection only (parser disabled).
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null; # do not add to config unless specified
|
||||
};
|
||||
|
||||
"run-as" = {
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "suricata";
|
||||
description = "Run Suricata with a specific user-id";
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "suricata";
|
||||
description = "Run Suricata with a specific group-id";
|
||||
};
|
||||
};
|
||||
|
||||
"host-mode" = mkOption {
|
||||
type = types.enum [
|
||||
"router"
|
||||
"sniffer-only"
|
||||
"auto"
|
||||
];
|
||||
default = "auto";
|
||||
description = ''
|
||||
If the Suricata box is a router for the sniffed networks, set it to 'router'. If
|
||||
it is a pure sniffing setup, set it to 'sniffer-only'. If set to auto, the variable
|
||||
is internally switched to 'router' in IPS mode and 'sniffer-only' in IDS mode.
|
||||
This feature is currently only used by the reject* keywords.
|
||||
'';
|
||||
};
|
||||
|
||||
"unix-command" = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (submodule {
|
||||
options = {
|
||||
enabled = mkOption {
|
||||
type = types.either types.bool (types.enum [ "auto" ]);
|
||||
default = "auto";
|
||||
};
|
||||
filename = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/suricata/suricata-command.socket";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
description = ''
|
||||
Unix command socket that can be used to pass commands to Suricata.
|
||||
An external tool can then connect to get information from Suricata
|
||||
or trigger some modifications of the engine. Set enabled to yes
|
||||
to activate the feature. In auto mode, the feature will only be
|
||||
activated in live capture mode. You can use the filename variable to set
|
||||
the file name of the socket.
|
||||
'';
|
||||
};
|
||||
|
||||
"exception-policy" = mkOption {
|
||||
type = types.enum [
|
||||
"auto"
|
||||
"drop-packet"
|
||||
"drop-flow"
|
||||
"reject"
|
||||
"bypass"
|
||||
"pass-packet"
|
||||
"pass-flow"
|
||||
"ignore"
|
||||
];
|
||||
default = "auto";
|
||||
description = ''
|
||||
Define a common behavior for all exception policies.
|
||||
In IPS mode, the default is drop-flow. For cases when that's not possible, the
|
||||
engine will fall to drop-packet. To fallback to old behavior (setting each of
|
||||
them individually, or ignoring all), set this to ignore.
|
||||
All values available for exception policies can be used, and there is one
|
||||
extra option: auto - which means drop-flow or drop-packet (as explained above)
|
||||
in IPS mode, and ignore in IDS mode. Exception policy values are: drop-packet,
|
||||
drop-flow, reject, bypass, pass-packet, pass-flow, ignore (disable).
|
||||
'';
|
||||
};
|
||||
|
||||
"default-rule-path" = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/suricata/rules";
|
||||
description = "Path in which suricata-update managed rules are stored by default";
|
||||
};
|
||||
|
||||
"rule-files" = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "suricata.rules" ];
|
||||
description = "Files to load suricata-update managed rules, relative to 'default-rule-path'";
|
||||
};
|
||||
|
||||
"classification-file" = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/suricata/rules/classification.config";
|
||||
description = "Suricata classification configuration file";
|
||||
};
|
||||
|
||||
"reference-config-file" = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.package}/etc/suricata/reference.config";
|
||||
description = "Suricata reference configuration file";
|
||||
};
|
||||
|
||||
"threshold-file" = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.package}/etc/suricata/threshold.config";
|
||||
description = "Suricata threshold configuration file";
|
||||
};
|
||||
|
||||
includes = mkOption {
|
||||
type = with types; nullOr (listOf path);
|
||||
default = null;
|
||||
description = ''
|
||||
Files to include in the suricata configuration. See
|
||||
[docs/configuration/suricata-yaml](https://docs.suricata.io/en/suricata-7.0.3/configuration/suricata-yaml.html)
|
||||
for available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
@ -942,6 +942,7 @@ in {
|
||||
sudo = handleTest ./sudo.nix {};
|
||||
sudo-rs = handleTest ./sudo-rs.nix {};
|
||||
sunshine = handleTest ./sunshine.nix {};
|
||||
suricata = handleTest ./suricata.nix {};
|
||||
suwayomi-server = handleTest ./suwayomi-server.nix {};
|
||||
swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
|
||||
swap-partition = handleTest ./swap-partition.nix {};
|
||||
|
86
nixos/tests/suricata.nix
Normal file
86
nixos/tests/suricata.nix
Normal file
@ -0,0 +1,86 @@
|
||||
import ./make-test-python.nix (
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
name = "suricata";
|
||||
meta.maintainers = with lib.maintainers; [ felbinger ];
|
||||
|
||||
nodes = {
|
||||
ids = {
|
||||
imports = [
|
||||
../modules/profiles/minimal.nix
|
||||
../modules/services/networking/suricata/default.nix
|
||||
];
|
||||
|
||||
networking.interfaces.eth1 = {
|
||||
useDHCP = false;
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.1.2";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# disable suricata-update because this requires an Internet connection
|
||||
systemd.services.suricata-update.enable = false;
|
||||
|
||||
# install suricata package to make suricatasc program available
|
||||
environment.systemPackages = with pkgs; [ suricata ];
|
||||
|
||||
services.suricata = {
|
||||
enable = true;
|
||||
settings = {
|
||||
vars.address-groups.HOME_NET = "192.168.1.0/24";
|
||||
unix-command.enabled = true;
|
||||
outputs = [ { fast.enabled = true; } ];
|
||||
af-packet = [ { interface = "eth1"; } ];
|
||||
classification-file = "${pkgs.suricata}/etc/suricata/classification.config";
|
||||
};
|
||||
};
|
||||
|
||||
# create suricata.rules with the rule to detect the output of the id command
|
||||
systemd.tmpfiles.rules = [
|
||||
''f /var/lib/suricata/rules/suricata.rules 644 suricata suricata 0 alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2019_07_26;)''
|
||||
];
|
||||
};
|
||||
helper = {
|
||||
imports = [ ../modules/profiles/minimal.nix ];
|
||||
|
||||
networking.interfaces.eth1 = {
|
||||
useDHCP = false;
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.1.1";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."localhost".locations = {
|
||||
"/id/".return = "200 'uid=0(root) gid=0(root) groups=0(root)'";
|
||||
};
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# check that configuration has been applied correctly with suricatasc
|
||||
with subtest("suricata configuration test"):
|
||||
ids.wait_for_unit("suricata.service")
|
||||
assert '1' in ids.succeed("suricatasc -c 'iface-list' | ${pkgs.jq}/bin/jq .message.count")
|
||||
|
||||
# test detection of events based on a static ruleset (output of id command)
|
||||
with subtest("suricata rule test"):
|
||||
helper.wait_for_unit("nginx.service")
|
||||
ids.wait_for_unit("suricata.service")
|
||||
|
||||
ids.succeed("curl http://192.168.1.1/id/")
|
||||
assert "id check returned root [**] [Classification: Potentially Bad Traffic]" in ids.succeed("tail -n 1 /var/log/suricata/fast.log"), "Suricata didn't detect the output of id comment"
|
||||
'';
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue
Block a user