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