at 25.11-pre 8.4 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 cfg = config.services.suricata; 9 pkg = cfg.package; 10 yaml = pkgs.formats.yaml { }; 11 inherit (lib) 12 mkEnableOption 13 mkPackageOption 14 mkOption 15 types 16 literalExpression 17 filterAttrsRecursive 18 concatStringsSep 19 strings 20 lists 21 mkIf 22 ; 23in 24{ 25 meta.maintainers = with lib.maintainers; [ felbinger ]; 26 27 options.services.suricata = { 28 enable = mkEnableOption "Suricata"; 29 30 package = mkPackageOption pkgs "suricata" { }; 31 32 configFile = mkOption { 33 type = types.path; 34 visible = false; 35 default = pkgs.writeTextFile { 36 name = "suricata.yaml"; 37 text = '' 38 %YAML 1.1 39 --- 40 ${builtins.readFile ( 41 yaml.generate "suricata-settings-raw.yaml" ( 42 filterAttrsRecursive (name: value: value != null) cfg.settings 43 ) 44 )} 45 ''; 46 }; 47 description = '' 48 Configuration file for suricata. 49 50 It is not usual to override the default values; it is recommended to use `settings`. 51 If you want to include extra configuration to the file, use the `settings.includes`. 52 ''; 53 }; 54 55 settings = mkOption { 56 type = types.submodule (import ./settings.nix { inherit config lib yaml; }); 57 example = literalExpression '' 58 vars.address-groups.HOME_NET = "192.168.178.0/24"; 59 outputs = [ 60 { 61 fast = { 62 enabled = true; 63 filename = "fast.log"; 64 append = "yes"; 65 }; 66 } 67 { 68 eve-log = { 69 enabled = true; 70 filetype = "regular"; 71 filename = "eve.json"; 72 community-id = true; 73 types = [ 74 { 75 alert.tagged-packets = "yes"; 76 } 77 ]; 78 }; 79 } 80 ]; 81 af-packet = [ 82 { 83 interface = "eth0"; 84 cluster-id = "99"; 85 cluster-type = "cluster_flow"; 86 defrag = "yes"; 87 } 88 { 89 interface = "default"; 90 } 91 ]; 92 af-xdp = [ 93 { 94 interface = "eth1"; 95 } 96 ]; 97 dpdk.interfaces = [ 98 { 99 interface = "eth2"; 100 } 101 ]; 102 pcap = [ 103 { 104 interface = "eth3"; 105 } 106 ]; 107 app-layer.protocols = { 108 telnet.enabled = "yes"; 109 dnp3.enabled = "yes"; 110 modbus.enabled = "yes"; 111 }; 112 ''; 113 description = "Suricata settings"; 114 }; 115 116 enabledSources = mkOption { 117 type = types.listOf types.str; 118 # see: nix-shell -p suricata python3Packages.pyyaml --command 'suricata-update list-sources' 119 default = [ 120 "et/open" 121 "etnetera/aggressive" 122 "stamus/lateral" 123 "oisf/trafficid" 124 "tgreen/hunting" 125 "sslbl/ja3-fingerprints" 126 "sslbl/ssl-fp-blacklist" 127 "malsilo/win-malware" 128 "pawpatrules" 129 ]; 130 description = '' 131 List of sources that should be enabled. 132 Currently sources which require a secret-code are not supported. 133 ''; 134 }; 135 136 disabledRules = mkOption { 137 type = types.listOf types.str; 138 # protocol dnp3 seams to be disabled, which causes the signature evaluation to fail, so we disable the 139 # dnp3 rules, see https://github.com/OISF/suricata/blob/master/rules/dnp3-events.rules for more details 140 default = [ 141 "2270000" 142 "2270001" 143 "2270002" 144 "2270003" 145 "2270004" 146 ]; 147 description = '' 148 List of rules that should be disabled. 149 ''; 150 }; 151 }; 152 153 config = 154 let 155 captureInterfaces = 156 let 157 inherit (lists) unique optionals; 158 in 159 unique ( 160 map (e: e.interface) ( 161 (optionals (cfg.settings.af-packet != null) cfg.settings.af-packet) 162 ++ (optionals (cfg.settings.af-xdp != null) cfg.settings.af-xdp) 163 ++ (optionals ( 164 cfg.settings.dpdk != null && cfg.settings.dpdk.interfaces != null 165 ) cfg.settings.dpdk.interfaces) 166 ++ (optionals (cfg.settings.pcap != null) cfg.settings.pcap) 167 ) 168 ); 169 in 170 mkIf cfg.enable { 171 assertions = [ 172 { 173 assertion = (builtins.length captureInterfaces) > 0; 174 message = '' 175 At least one capture interface must be configured: 176 - `services.suricata.settings.af-packet` 177 - `services.suricata.settings.af-xdp` 178 - `services.suricata.settings.dpdk.interfaces` 179 - `services.suricata.settings.pcap` 180 ''; 181 } 182 ]; 183 184 boot.kernelModules = mkIf (cfg.settings.af-packet != null) [ "af_packet" ]; 185 186 users = { 187 groups.${cfg.settings.run-as.group} = { }; 188 users.${cfg.settings.run-as.user} = { 189 group = cfg.settings.run-as.group; 190 isSystemUser = true; 191 }; 192 }; 193 194 systemd.tmpfiles.rules = [ 195 "d ${cfg.settings."default-log-dir"} 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}" 196 "d /var/lib/suricata 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}" 197 "d ${cfg.settings."default-rule-path"} 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}" 198 ]; 199 200 systemd.services = { 201 suricata-update = { 202 description = "Update Suricata Rules"; 203 wantedBy = [ "multi-user.target" ]; 204 wants = [ "network-online.target" ]; 205 after = [ "network-online.target" ]; 206 207 script = 208 let 209 python = pkgs.python3.withPackages (ps: with ps; [ pyyaml ]); 210 enabledSourcesCmds = map ( 211 src: "${python.interpreter} ${pkg}/bin/suricata-update enable-source ${src}" 212 ) cfg.enabledSources; 213 in 214 '' 215 ${concatStringsSep "\n" enabledSourcesCmds} 216 ${python.interpreter} ${pkg}/bin/suricata-update update-sources 217 ${python.interpreter} ${pkg}/bin/suricata-update update --suricata-conf ${cfg.configFile} --no-test \ 218 --disable-conf ${pkgs.writeText "suricata-disable-conf" "${concatStringsSep "\n" cfg.disabledRules}"} 219 ''; 220 serviceConfig = { 221 Type = "oneshot"; 222 223 PrivateTmp = true; 224 PrivateDevices = true; 225 PrivateIPC = true; 226 227 DynamicUser = true; 228 User = cfg.settings.run-as.user; 229 Group = cfg.settings.run-as.group; 230 231 ReadOnlyPaths = cfg.configFile; 232 ReadWritePaths = [ 233 "/var/lib/suricata" 234 cfg.settings."default-rule-path" 235 ]; 236 }; 237 }; 238 suricata = { 239 description = "Suricata"; 240 wantedBy = [ "multi-user.target" ]; 241 after = [ "suricata-update.service" ]; 242 serviceConfig = 243 let 244 interfaceOptions = strings.concatMapStrings (interface: " -i ${interface}") captureInterfaces; 245 in 246 { 247 ExecStartPre = "!${pkg}/bin/suricata -c ${cfg.configFile} -T"; 248 ExecStart = "!${pkg}/bin/suricata -c ${cfg.configFile}${interfaceOptions}"; 249 Restart = "on-failure"; 250 251 User = cfg.settings.run-as.user; 252 Group = cfg.settings.run-as.group; 253 254 NoNewPrivileges = true; 255 PrivateTmp = true; 256 PrivateDevices = true; 257 PrivateIPC = true; 258 ProtectSystem = "strict"; 259 DevicePolicy = "closed"; 260 LockPersonality = true; 261 MemoryDenyWriteExecute = true; 262 ProtectHostname = true; 263 ProtectProc = true; 264 ProtectKernelLogs = true; 265 ProtectKernelModules = true; 266 ProtectKernelTunables = true; 267 ProtectControlGroups = true; 268 ProcSubset = "pid"; 269 RestrictNamespaces = true; 270 RestrictRealtime = true; 271 RestrictSUIDSGID = true; 272 SystemCallArchitectures = "native"; 273 RemoveIPC = true; 274 275 ReadOnlyPaths = cfg.configFile; 276 ReadWritePaths = cfg.settings."default-log-dir"; 277 RuntimeDirectory = "suricata"; 278 }; 279 }; 280 }; 281 }; 282}