lol

nixos/crowdsec: init module (#437310)

authored by

Matt Sturgeon and committed by
GitHub
0934f3ba 68310c74

+978
+5
maintainers/maintainer-list.nix
··· 15287 15287 github = "M0NsTeRRR"; 15288 15288 githubId = 37785089; 15289 15289 }; 15290 + m0ustache3 = { 15291 + name = "M0ustach3"; 15292 + github = "M0ustach3"; 15293 + githubId = 37956764; 15294 + }; 15290 15295 m1cr0man = { 15291 15296 email = "lucas+nix@m1cr0man.com"; 15292 15297 github = "m1cr0man";
+973
nixos/modules/services/security/crowdsec.nix
··· 1 + { 2 + config, 3 + pkgs, 4 + lib, 5 + ... 6 + }: 7 + let 8 + 9 + format = pkgs.formats.yaml { }; 10 + 11 + rootDir = "/var/lib/crowdsec"; 12 + stateDir = "${rootDir}/state"; 13 + confDir = "/etc/crowdsec/"; 14 + hubDir = "${stateDir}/hub/"; 15 + notificationsDir = "${confDir}/notifications/"; 16 + pluginDir = "${confDir}/plugins/"; 17 + parsersDir = "${confDir}/parsers/"; 18 + localPostOverflowsDir = "${confDir}/postoverflows/"; 19 + localPostOverflowsS01WhitelistDir = "${localPostOverflowsDir}/s01-whitelist/"; 20 + localScenariosDir = "${confDir}/scenarios/"; 21 + localParsersS00RawDir = "${parsersDir}/s00-raw/"; 22 + localParsersS01ParseDir = "${parsersDir}/s01-parse/"; 23 + localParsersS02EnrichDir = "${parsersDir}/s02-enrich/"; 24 + localContextsDir = "${confDir}/contexts/"; 25 + 26 + in 27 + { 28 + 29 + options.services.crowdsec = { 30 + enable = lib.mkEnableOption "CrowdSec Security Engine"; 31 + 32 + package = lib.mkPackageOption pkgs "crowdsec" { }; 33 + 34 + autoUpdateService = lib.mkEnableOption "if `true` `cscli hub update` will be executed daily. See `https://docs.crowdsec.net/docs/cscli/cscli_hub_update/` for more information"; 35 + 36 + openFirewall = lib.mkOption { 37 + type = lib.types.bool; 38 + default = false; 39 + example = true; 40 + description = '' 41 + Whether to automatically open firewall ports for `crowdsec`. 42 + ''; 43 + }; 44 + 45 + user = lib.mkOption { 46 + type = lib.types.str; 47 + description = "The user to run crowdsec as"; 48 + default = "crowdsec"; 49 + }; 50 + 51 + group = lib.mkOption { 52 + type = lib.types.str; 53 + description = "The group to run crowdsec as"; 54 + default = "crowdsec"; 55 + }; 56 + 57 + name = lib.mkOption { 58 + type = lib.types.str; 59 + description = '' 60 + Name of the machine when registering it at the central or local api. 61 + ''; 62 + default = config.networking.hostName; 63 + defaultText = lib.literalExpression "config.networking.hostName"; 64 + }; 65 + 66 + localConfig = lib.mkOption { 67 + type = lib.types.submodule { 68 + options = { 69 + acquisitions = lib.mkOption { 70 + type = lib.types.listOf format.type; 71 + default = [ ]; 72 + description = '' 73 + A list of acquisition specifications, which define the data sources you want to be parsed. 74 + 75 + See <https://docs.crowdsec.net/docs/data_sources/intro> for details. 76 + ''; 77 + example = [ 78 + { 79 + source = "journalctl"; 80 + journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; 81 + labels = { 82 + type = "syslog"; 83 + }; 84 + } 85 + ]; 86 + }; 87 + scenarios = lib.mkOption { 88 + type = lib.types.listOf format.type; 89 + default = [ ]; 90 + description = '' 91 + A list of scenarios specifications. 92 + 93 + See <https://docs.crowdsec.net/docs/scenarios/intro> for details. 94 + ''; 95 + example = [ 96 + { 97 + type = "leaky"; 98 + name = "crowdsecurity/myservice-bf"; 99 + description = "Detect myservice bruteforce"; 100 + filter = "evt.Meta.log_type == 'myservice_failed_auth'"; 101 + leakspeed = "10s"; 102 + capacity = 5; 103 + groupby = "evt.Meta.source_ip"; 104 + } 105 + ]; 106 + }; 107 + parsers = lib.mkOption { 108 + type = lib.types.submodule { 109 + options = { 110 + s00Raw = lib.mkOption { 111 + type = lib.types.listOf format.type; 112 + default = [ ]; 113 + description = '' 114 + A list of stage s00-raw specifications. Most of the time, those are already included in the hub, but are presented here anyway. 115 + 116 + See <https://docs.crowdsec.net/docs/parsers/intro> for details. 117 + ''; 118 + }; 119 + s01Parse = lib.mkOption { 120 + type = lib.types.listOf format.type; 121 + default = [ ]; 122 + description = '' 123 + A list of stage s01-parse specifications. 124 + 125 + See <https://docs.crowdsec.net/docs/parsers/intro> for details. 126 + ''; 127 + example = [ 128 + { 129 + filter = "1=1"; 130 + debug = true; 131 + onsuccess = "next_stage"; 132 + name = "example/custom-service-logs"; 133 + description = "Parsing custom service logs"; 134 + grok = { 135 + pattern = "^%{DATA:some_data}$"; 136 + apply_on = "message"; 137 + }; 138 + statics = [ 139 + { 140 + parsed = "is_my_custom_service"; 141 + value = "yes"; 142 + } 143 + ]; 144 + } 145 + ]; 146 + }; 147 + s02Enrich = lib.mkOption { 148 + type = lib.types.listOf format.type; 149 + default = [ ]; 150 + description = '' 151 + A list of stage s02-enrich specifications. Inside this list, you can specify Parser Whitelists. 152 + 153 + See <https://docs.crowdsec.net/docs/whitelist/intro> for details. 154 + ''; 155 + example = [ 156 + { 157 + name = "myips/whitelist"; 158 + description = "Whitelist parse events from my IPs"; 159 + whitelist = { 160 + reason = "My IP ranges"; 161 + ip = [ 162 + "1.2.3.4" 163 + ]; 164 + cidr = [ 165 + "1.2.3.0/24" 166 + ]; 167 + }; 168 + } 169 + ]; 170 + }; 171 + }; 172 + }; 173 + default = { }; 174 + }; 175 + postOverflows = lib.mkOption { 176 + type = lib.types.submodule { 177 + options = { 178 + s01Whitelist = lib.mkOption { 179 + type = lib.types.listOf format.type; 180 + default = [ ]; 181 + description = '' 182 + A list of stage s01-whitelist specifications. Inside this list, you can specify Postoverflows Whitelists. 183 + 184 + See <https://docs.crowdsec.net/docs/whitelist/intro> for details. 185 + ''; 186 + example = [ 187 + { 188 + name = "postoverflows/whitelist_my_dns_domain"; 189 + description = "Whitelist my reverse DNS"; 190 + whitelist = { 191 + reason = "Don't ban me"; 192 + expression = [ 193 + "evt.Enriched.reverse_dns endsWith '.local.'" 194 + ]; 195 + }; 196 + } 197 + ]; 198 + }; 199 + }; 200 + }; 201 + default = { }; 202 + }; 203 + contexts = lib.mkOption { 204 + type = lib.types.listOf format.type; 205 + description = '' 206 + A list of additional contexts to specify. 207 + 208 + See <https://docs.crowdsec.net/docs/next/log_processor/alert_context/intro> for details. 209 + ''; 210 + example = [ 211 + { 212 + context = { 213 + target_uri = [ "evt.Meta.http_path" ]; 214 + user_agent = [ "evt.Meta.http_user_agent" ]; 215 + method = [ "evt.Meta.http_verb" ]; 216 + status = [ "evt.Meta.http_status" ]; 217 + }; 218 + } 219 + ]; 220 + default = [ ]; 221 + }; 222 + notifications = lib.mkOption { 223 + type = lib.types.listOf format.type; 224 + description = '' 225 + A list of notifications to enable and use in your profiles. Note that for now, only the plugins shipped by default with CrowdSec are supported. 226 + 227 + See <https://docs.crowdsec.net/docs/notification_plugins/intro> for details. 228 + ''; 229 + example = [ 230 + { 231 + type = "http"; 232 + name = "default_http_notification"; 233 + log_level = "info"; 234 + format = '' 235 + {{.|toJson}} 236 + ''; 237 + url = "https://example.com/hook"; 238 + method = "POST"; 239 + } 240 + ]; 241 + default = [ ]; 242 + }; 243 + profiles = lib.mkOption { 244 + type = lib.types.listOf format.type; 245 + description = '' 246 + A list of profiles to enable. 247 + 248 + See <https://docs.crowdsec.net/docs/profiles/intro> for more details. 249 + ''; 250 + example = [ 251 + { 252 + name = "default_ip_remediation"; 253 + filters = [ 254 + "Alert.Remediation == true && Alert.GetScope() == 'Ip'" 255 + ]; 256 + decisions = [ 257 + { 258 + type = "ban"; 259 + duration = "4h"; 260 + } 261 + ]; 262 + on_success = "break"; 263 + } 264 + { 265 + name = "default_range_remediation"; 266 + filters = [ 267 + "Alert.Remediation == true && Alert.GetScope() == 'Range'" 268 + ]; 269 + decisions = [ 270 + { 271 + type = "ban"; 272 + duration = "4h"; 273 + } 274 + ]; 275 + on_success = "break"; 276 + } 277 + ]; 278 + default = [ 279 + { 280 + name = "default_ip_remediation"; 281 + filters = [ 282 + "Alert.Remediation == true && Alert.GetScope() == 'Ip'" 283 + ]; 284 + decisions = [ 285 + { 286 + type = "ban"; 287 + duration = "4h"; 288 + } 289 + ]; 290 + on_success = "break"; 291 + } 292 + { 293 + name = "default_range_remediation"; 294 + filters = [ 295 + "Alert.Remediation == true && Alert.GetScope() == 'Range'" 296 + ]; 297 + decisions = [ 298 + { 299 + type = "ban"; 300 + duration = "4h"; 301 + } 302 + ]; 303 + on_success = "break"; 304 + } 305 + ]; 306 + }; 307 + patterns = lib.mkOption { 308 + type = lib.types.listOf lib.types.package; 309 + default = [ ]; 310 + example = lib.literalExpression '' 311 + [ (pkgs.writeTextDir "custom_service_logs" (builtins.readFile ./custom_service_logs)) ] 312 + ''; 313 + }; 314 + }; 315 + }; 316 + default = { }; 317 + }; 318 + 319 + hub = lib.mkOption { 320 + type = lib.types.submodule { 321 + options = { 322 + collections = lib.mkOption { 323 + type = lib.types.listOf lib.types.str; 324 + default = [ ]; 325 + description = "List of hub collections to install"; 326 + example = [ "crowdsecurity/linux" ]; 327 + }; 328 + 329 + scenarios = lib.mkOption { 330 + type = lib.types.listOf lib.types.str; 331 + default = [ ]; 332 + description = "List of hub scenarios to install"; 333 + example = [ "crowdsecurity/ssh-bf" ]; 334 + }; 335 + 336 + parsers = lib.mkOption { 337 + type = lib.types.listOf lib.types.str; 338 + default = [ ]; 339 + description = "List of hub parsers to install"; 340 + example = [ "crowdsecurity/sshd-logs" ]; 341 + }; 342 + 343 + postOverflows = lib.mkOption { 344 + type = lib.types.listOf lib.types.str; 345 + default = [ ]; 346 + description = "List of hub postoverflows to install"; 347 + example = [ "crowdsecurity/auditd-nix-wrappers-whitelist-process" ]; 348 + }; 349 + 350 + appSecConfigs = lib.mkOption { 351 + type = lib.types.listOf lib.types.str; 352 + default = [ ]; 353 + description = "List of hub appsec configurations to install"; 354 + example = [ "crowdsecurity/appsec-default" ]; 355 + }; 356 + 357 + appSecRules = lib.mkOption { 358 + type = lib.types.listOf lib.types.str; 359 + default = [ ]; 360 + description = "List of hub appsec rules to install"; 361 + example = [ "crowdsecurity/base-config" ]; 362 + }; 363 + 364 + branch = lib.mkOption { 365 + type = lib.types.str; 366 + default = "master"; 367 + description = '' 368 + The git branch on which cscli is going to fetch configurations. 369 + 370 + See `https://docs.crowdsec.net/docs/configuration/crowdsec_configuration/#hub_branch` for more information. 371 + ''; 372 + example = [ 373 + "master" 374 + "v1.4.3" 375 + "v1.4.2" 376 + ]; 377 + }; 378 + }; 379 + }; 380 + default = { }; 381 + description = '' 382 + Hub collections, parsers, AppSec rules, etc. 383 + ''; 384 + }; 385 + 386 + settings = lib.mkOption { 387 + type = lib.types.submodule { 388 + options = { 389 + general = lib.mkOption { 390 + description = '' 391 + Settings for the main CrowdSec configuration file. 392 + 393 + Refer to the defaults at <https://github.com/crowdsecurity/crowdsec/blob/master/config/config.yaml>. 394 + ''; 395 + type = format.type; 396 + default = { }; 397 + }; 398 + simulation = lib.mkOption { 399 + type = format.type; 400 + default = { 401 + simulation = false; 402 + }; 403 + description = '' 404 + Attributes inside the simulation.yaml file. 405 + ''; 406 + }; 407 + 408 + lapi = lib.mkOption { 409 + type = lib.types.submodule { 410 + options = { 411 + credentialsFile = lib.mkOption { 412 + type = lib.types.nullOr lib.types.path; 413 + example = "/run/crowdsec/lapi.yaml"; 414 + description = '' 415 + The LAPI credential file to use. 416 + ''; 417 + default = null; 418 + }; 419 + }; 420 + }; 421 + description = '' 422 + LAPI Configuration attributes 423 + ''; 424 + default = { }; 425 + }; 426 + capi = lib.mkOption { 427 + type = lib.types.submodule { 428 + options = { 429 + credentialsFile = lib.mkOption { 430 + type = lib.types.nullOr lib.types.path; 431 + example = "/run/crowdsec/capi.yaml"; 432 + description = '' 433 + The CAPI credential file to use. 434 + ''; 435 + default = null; 436 + }; 437 + }; 438 + }; 439 + description = '' 440 + CAPI Configuration attributes 441 + ''; 442 + default = { }; 443 + }; 444 + console = lib.mkOption { 445 + type = lib.types.submodule { 446 + options = { 447 + tokenFile = lib.mkOption { 448 + type = lib.types.nullOr lib.types.path; 449 + example = "/run/crowdsec/console_token.yaml"; 450 + description = '' 451 + The Console Token file to use. 452 + ''; 453 + default = null; 454 + }; 455 + configuration = lib.mkOption { 456 + type = format.type; 457 + default = { 458 + share_manual_decisions = false; 459 + share_custom = false; 460 + share_tainted = false; 461 + share_context = false; 462 + }; 463 + description = '' 464 + Attributes inside the console.yaml file. 465 + ''; 466 + }; 467 + }; 468 + }; 469 + description = '' 470 + Console Configuration attributes 471 + ''; 472 + default = { }; 473 + }; 474 + }; 475 + }; 476 + }; 477 + }; 478 + config = 479 + let 480 + cfg = config.services.crowdsec; 481 + configFile = format.generate "crowdsec.yaml" cfg.settings.general; 482 + simulationFile = format.generate "simulation.yaml" cfg.settings.simulation; 483 + consoleFile = format.generate "console.yaml" cfg.settings.console.configuration; 484 + patternsDir = pkgs.buildPackages.symlinkJoin { 485 + name = "crowdsec-patterns"; 486 + paths = [ 487 + cfg.localConfig.patterns 488 + "${lib.attrsets.getOutput "out" cfg.package}/share/crowdsec/config/patterns/" 489 + ]; 490 + }; 491 + 492 + cscli = pkgs.writeShellScriptBin "cscli" '' 493 + set -euo pipefail 494 + # cscli needs crowdsec on it's path in order to be able to run `cscli explain` 495 + export PATH="$PATH:${lib.makeBinPath [ cfg.package ]}" 496 + sudo=exec 497 + if [ "$USER" != "${cfg.user}" ]; then 498 + ${ 499 + if config.security.sudo.enable then 500 + "sudo='exec ${config.security.wrapperDir}/sudo -u ${cfg.user}'" 501 + else 502 + ">&2 echo 'Aborting, cscli must be run as user `${cfg.user}`!'; exit 2" 503 + } 504 + fi 505 + $sudo ${lib.getExe' cfg.package "cscli"} -c=${configFile} "$@" 506 + ''; 507 + 508 + localScenariosMap = (map (format.generate "scenario.yaml") cfg.localConfig.scenarios); 509 + localParsersS00RawMap = ( 510 + map (format.generate "parsers-s00-raw.yaml") cfg.localConfig.parsers.s00Raw 511 + ); 512 + localParsersS01ParseMap = ( 513 + map (format.generate "parsers-s01-parse.yaml") cfg.localConfig.parsers.s01Parse 514 + ); 515 + localParsersS02EnrichMap = ( 516 + map (format.generate "parsers-s02-enrich.yaml") cfg.localConfig.parsers.s02Enrich 517 + ); 518 + localPostOverflowsS01WhitelistMap = ( 519 + map (format.generate "postoverflows-s01-whitelist.yaml") cfg.localConfig.postOverflows.s01Whitelist 520 + ); 521 + localContextsMap = (map (format.generate "context.yaml") cfg.localConfig.contexts); 522 + localNotificationsMap = (map (format.generate "notification.yaml") cfg.localConfig.notifications); 523 + localProfilesFile = pkgs.writeText "local_profiles.yaml" '' 524 + --- 525 + ${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.profiles} 526 + --- 527 + ''; 528 + localAcquisisionFile = pkgs.writeText "local_acquisisions.yaml" '' 529 + --- 530 + ${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.acquisitions} 531 + --- 532 + ''; 533 + 534 + scriptArray = [ 535 + "set -euo pipefail" 536 + "${lib.getExe' pkgs.coreutils "mkdir"} -p '${hubDir}'" 537 + "${lib.getExe cscli} hub update" 538 + ] 539 + ++ lib.optionals (cfg.hub.collections != [ ]) [ 540 + "${lib.getExe cscli} collections install ${ 541 + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.collections 542 + }" 543 + ] 544 + ++ lib.optionals (cfg.hub.scenarios != [ ]) [ 545 + "${lib.getExe cscli} scenarios install ${ 546 + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.scenarios 547 + }" 548 + ] 549 + ++ lib.optionals (cfg.hub.parsers != [ ]) [ 550 + "${lib.getExe cscli} parsers install ${ 551 + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.parsers 552 + }" 553 + ] 554 + ++ lib.optionals (cfg.hub.postOverflows != [ ]) [ 555 + "${lib.getExe cscli} postoverflows install ${ 556 + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.postOverflows 557 + }" 558 + ] 559 + ++ lib.optionals (cfg.hub.appSecConfigs != [ ]) [ 560 + "${lib.getExe cscli} appsec-configs install ${ 561 + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecConfigs 562 + }" 563 + ] 564 + ++ lib.optionals (cfg.hub.appSecRules != [ ]) [ 565 + "${lib.getExe cscli} appsec-rules install ${ 566 + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecRules 567 + }" 568 + ] 569 + ++ lib.optionals (cfg.settings.general.api.server.enable) [ 570 + '' 571 + if [ ! -s "${cfg.settings.general.api.client.credentials_path}" ]; then 572 + ${lib.getExe cscli} machine add "${cfg.name}" --auto 573 + fi 574 + '' 575 + ] 576 + ++ lib.optionals (cfg.settings.capi.credentialsFile != null) [ 577 + '' 578 + if ! grep -q password "${cfg.settings.capi.credentialsFile}" ]; then 579 + ${lib.getExe cscli} capi register 580 + fi 581 + '' 582 + ] 583 + ++ lib.optionals (cfg.settings.console.tokenFile != null) [ 584 + '' 585 + if [ ! -e "${cfg.settings.console.tokenFile}" ]; then 586 + ${lib.getExe cscli} console enroll "$(cat ${cfg.settings.console.tokenFile})" --name ${cfg.name} 587 + fi 588 + '' 589 + ]; 590 + 591 + setupScript = pkgs.writeShellScriptBin "crowdsec-setup" ( 592 + lib.strings.concatStringsSep "\n" scriptArray 593 + ); 594 + 595 + in 596 + lib.mkIf (cfg.enable) { 597 + 598 + warnings = 599 + [ ] 600 + ++ lib.optionals (cfg.localConfig.profiles == [ ]) [ 601 + "By not specifying profiles in services.crowdsec.localConfig.profiles, CrowdSec will not react to any alert by default." 602 + ] 603 + ++ lib.optionals (cfg.localConfig.acquisitions == [ ]) [ 604 + "By not specifying acquisitions in services.crowdsec.localConfig.acquisitions, CrowdSec will not look for any data source." 605 + ]; 606 + 607 + services.crowdsec.settings.general = { 608 + common = { 609 + daemonize = false; 610 + log_media = "stdout"; 611 + }; 612 + config_paths = { 613 + config_dir = confDir; 614 + data_dir = stateDir; 615 + simulation_path = simulationFile; 616 + hub_dir = hubDir; 617 + index_path = lib.strings.normalizePath "${stateDir}/hub/.index.json"; 618 + notification_dir = notificationsDir; 619 + plugin_dir = pluginDir; 620 + pattern_dir = patternsDir; 621 + }; 622 + db_config = { 623 + type = lib.mkDefault "sqlite"; 624 + db_path = lib.mkDefault (lib.strings.normalizePath "${stateDir}/crowdsec.db"); 625 + use_wal = lib.mkDefault true; 626 + }; 627 + crowdsec_service = { 628 + enable = lib.mkDefault true; 629 + acquisition_path = lib.mkDefault localAcquisisionFile; 630 + }; 631 + api = { 632 + client = { 633 + credentials_path = cfg.settings.lapi.credentialsFile; 634 + }; 635 + server = { 636 + enable = lib.mkDefault false; 637 + listen_uri = lib.mkDefault "127.0.0.1:8080"; 638 + 639 + console_path = lib.mkDefault consoleFile; 640 + profiles_path = lib.mkDefault localProfilesFile; 641 + 642 + online_client = lib.mkDefault { 643 + sharing = lib.mkDefault true; 644 + pull = lib.mkDefault { 645 + community = lib.mkDefault true; 646 + blocklists = lib.mkDefault true; 647 + }; 648 + credentials_path = cfg.settings.capi.credentialsFile; 649 + }; 650 + }; 651 + }; 652 + prometheus = { 653 + enabled = lib.mkDefault true; 654 + level = lib.mkDefault "full"; 655 + listen_addr = lib.mkDefault "127.0.0.1"; 656 + listen_port = lib.mkDefault 6060; 657 + }; 658 + cscli = { 659 + hub_branch = cfg.hub.branch; 660 + }; 661 + }; 662 + 663 + environment = { 664 + systemPackages = [ cscli ]; 665 + }; 666 + 667 + systemd.packages = [ cfg.package ]; 668 + 669 + systemd.timers.crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) { 670 + description = "Update the crowdsec hub index"; 671 + wantedBy = [ "timers.target" ]; 672 + timerConfig = { 673 + OnCalendar = "daily"; 674 + RandomizedDelaySec = 300; 675 + Persistent = "yes"; 676 + Unit = "crowdsec-update-hub.service"; 677 + }; 678 + }; 679 + systemd.services = { 680 + crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) { 681 + description = "Update the crowdsec hub index"; 682 + serviceConfig = { 683 + Type = "oneshot"; 684 + User = cfg.user; 685 + Group = cfg.group; 686 + LimitNOFILE = 65536; 687 + NoNewPrivileges = true; 688 + LockPersonality = true; 689 + RemoveIPC = true; 690 + ReadWritePaths = [ 691 + rootDir 692 + confDir 693 + ]; 694 + ProtectSystem = "strict"; 695 + PrivateUsers = true; 696 + ProtectHome = true; 697 + PrivateTmp = true; 698 + PrivateDevices = true; 699 + ProtectHostname = true; 700 + UMask = "0077"; 701 + ProtectKernelTunables = true; 702 + ProtectKernelModules = true; 703 + ProtectControlGroups = true; 704 + ProtectProc = "invisible"; 705 + SystemCallFilter = [ 706 + " " # This is needed to clear the SystemCallFilter existing definitions 707 + "~@reboot" 708 + "~@swap" 709 + "~@obsolete" 710 + "~@mount" 711 + "~@module" 712 + "~@debug" 713 + "~@cpu-emulation" 714 + "~@clock" 715 + "~@raw-io" 716 + "~@privileged" 717 + "~@resources" 718 + ]; 719 + CapabilityBoundingSet = [ 720 + " " # Reset all capabilities to an empty set 721 + ]; 722 + RestrictAddressFamilies = [ 723 + " " # This is needed to clear the RestrictAddressFamilies existing definitions 724 + "none" # Remove all addresses families 725 + "AF_UNIX" 726 + "AF_INET" 727 + "AF_INET6" 728 + ]; 729 + DevicePolicy = "closed"; 730 + ProtectKernelLogs = true; 731 + SystemCallArchitectures = "native"; 732 + RestrictNamespaces = true; 733 + RestrictRealtime = true; 734 + RestrictSUIDSGID = true; 735 + ExecStart = "${lib.getExe cscli} --error hub update"; 736 + ExecStartPost = "systemctl reload crowdsec.service"; 737 + DynamicUser = true; 738 + }; 739 + }; 740 + 741 + crowdsec = { 742 + description = "CrowdSec agent"; 743 + wantedBy = [ "multi-user.target" ]; 744 + after = [ "network-online.target" ]; 745 + wants = [ "network-online.target" ]; 746 + path = lib.mkForce [ ]; 747 + environment = { 748 + LC_ALL = "C"; 749 + LANG = "C"; 750 + }; 751 + serviceConfig = { 752 + User = cfg.user; 753 + Group = cfg.group; 754 + Type = "simple"; 755 + RestartSec = 60; 756 + LimitNOFILE = 65536; 757 + NoNewPrivileges = true; 758 + LockPersonality = true; 759 + RemoveIPC = true; 760 + ReadWritePaths = [ 761 + rootDir 762 + confDir 763 + ]; 764 + ProtectSystem = "strict"; 765 + PrivateUsers = true; 766 + ProtectHome = true; 767 + PrivateTmp = true; 768 + PrivateDevices = true; 769 + ProtectHostname = true; 770 + ProtectClock = true; 771 + UMask = "0077"; 772 + ProtectKernelTunables = true; 773 + ProtectKernelModules = true; 774 + ProtectControlGroups = true; 775 + ProtectProc = "invisible"; 776 + SystemCallFilter = [ 777 + " " # This is needed to clear the SystemCallFilter existing definitions 778 + "~@reboot" 779 + "~@swap" 780 + "~@obsolete" 781 + "~@mount" 782 + "~@module" 783 + "~@debug" 784 + "~@cpu-emulation" 785 + "~@clock" 786 + "~@raw-io" 787 + "~@privileged" 788 + "~@resources" 789 + ]; 790 + CapabilityBoundingSet = [ 791 + " " # Reset all capabilities to an empty set 792 + "CAP_SYSLOG" # Add capability to read syslog 793 + ]; 794 + RestrictAddressFamilies = [ 795 + " " # This is needed to clear the RestrictAddressFamilies existing definitions 796 + "none" # Remove all addresses families 797 + "AF_UNIX" 798 + "AF_INET" 799 + "AF_INET6" 800 + ]; 801 + DevicePolicy = "closed"; 802 + ProtectKernelLogs = true; 803 + SystemCallArchitectures = "native"; 804 + DynamicUser = true; 805 + RestrictNamespaces = true; 806 + RestrictRealtime = true; 807 + RestrictSUIDSGID = true; 808 + ExecReload = [ 809 + " " # This is needed to clear the ExecReload definitions from upstream 810 + ]; 811 + ExecStart = [ 812 + " " # This is needed to clear the ExecStart definitions from upstream 813 + "${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -info" 814 + ]; 815 + ExecStartPre = [ 816 + " " # This is needed to clear the ExecStartPre definitions from upstream 817 + "${lib.getExe setupScript}" 818 + "${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -t -error" 819 + ]; 820 + }; 821 + }; 822 + }; 823 + 824 + systemd.tmpfiles.settings = { 825 + "10-crowdsec" = 826 + 827 + builtins.listToAttrs ( 828 + map 829 + (dirName: { 830 + inherit cfg; 831 + name = lib.strings.normalizePath dirName; 832 + value = { 833 + d = { 834 + user = cfg.user; 835 + group = cfg.group; 836 + mode = "0750"; 837 + }; 838 + }; 839 + }) 840 + [ 841 + stateDir 842 + hubDir 843 + confDir 844 + localScenariosDir 845 + localPostOverflowsDir 846 + localPostOverflowsS01WhitelistDir 847 + parsersDir 848 + localParsersS00RawDir 849 + localParsersS01ParseDir 850 + localParsersS02EnrichDir 851 + localContextsDir 852 + notificationsDir 853 + pluginDir 854 + ] 855 + ) 856 + // builtins.listToAttrs ( 857 + map (scenarioFile: { 858 + inherit cfg; 859 + name = lib.strings.normalizePath "${localScenariosDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf scenarioFile)}"; 860 + value = { 861 + link = { 862 + type = "L+"; 863 + argument = "${scenarioFile}"; 864 + }; 865 + }; 866 + }) localScenariosMap 867 + ) 868 + // builtins.listToAttrs ( 869 + map (parser: { 870 + inherit cfg; 871 + name = lib.strings.normalizePath "${localParsersS00RawDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; 872 + value = { 873 + link = { 874 + type = "L+"; 875 + argument = "${parser}"; 876 + }; 877 + }; 878 + }) localParsersS00RawMap 879 + ) 880 + // builtins.listToAttrs ( 881 + map (parser: { 882 + inherit cfg; 883 + name = lib.strings.normalizePath "${localParsersS01ParseDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; 884 + value = { 885 + link = { 886 + type = "L+"; 887 + argument = "${parser}"; 888 + }; 889 + }; 890 + }) localParsersS01ParseMap 891 + ) 892 + // builtins.listToAttrs ( 893 + map (parser: { 894 + inherit cfg; 895 + name = lib.strings.normalizePath "${localParsersS02EnrichDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; 896 + value = { 897 + link = { 898 + type = "L+"; 899 + argument = "${parser}"; 900 + }; 901 + }; 902 + }) localParsersS02EnrichMap 903 + ) 904 + // builtins.listToAttrs ( 905 + map (postoverflow: { 906 + inherit cfg; 907 + name = lib.strings.normalizePath "${localPostOverflowsS01WhitelistDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf postoverflow)}"; 908 + value = { 909 + link = { 910 + type = "L+"; 911 + argument = "${postoverflow}"; 912 + }; 913 + }; 914 + }) localPostOverflowsS01WhitelistMap 915 + ) 916 + // builtins.listToAttrs ( 917 + map (context: { 918 + inherit cfg; 919 + name = lib.strings.normalizePath "${localContextsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf context)}"; 920 + value = { 921 + link = { 922 + type = "L+"; 923 + argument = "${context}"; 924 + }; 925 + }; 926 + }) localContextsMap 927 + ) 928 + // builtins.listToAttrs ( 929 + map (notification: { 930 + inherit cfg; 931 + name = lib.strings.normalizePath "${notificationsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf notification)}"; 932 + value = { 933 + link = { 934 + type = "L+"; 935 + argument = "${notification}"; 936 + }; 937 + }; 938 + }) localNotificationsMap 939 + ); 940 + }; 941 + 942 + users.users.${cfg.user} = { 943 + name = cfg.user; 944 + description = lib.mkDefault "CrowdSec service user"; 945 + isSystemUser = true; 946 + group = cfg.group; 947 + extraGroups = [ "systemd-journal" ]; 948 + }; 949 + 950 + users.groups.${cfg.group} = lib.mapAttrs (name: lib.mkDefault) { }; 951 + 952 + networking.firewall.allowedTCPPorts = 953 + let 954 + parsePortFromURLOption = 955 + url: option: 956 + builtins.addErrorContext "extracting a port from URL: `${option}` requires a port to be specified, but we failed to parse a port from '${url}'" ( 957 + lib.strings.toInt (lib.last (lib.strings.splitString ":" url)) 958 + ); 959 + in 960 + lib.mkIf cfg.openFirewall [ 961 + cfg.settings.general.prometheus.listen_port 962 + (parsePortFromURLOption cfg.settings.general.api.server.listen_uri "config.services.crowdsec.settings.general.api.server.listen_uri") 963 + ]; 964 + }; 965 + 966 + meta = { 967 + maintainers = with lib.maintainers; [ 968 + m0ustach3 969 + tornax 970 + jk 971 + ]; 972 + }; 973 + }