···
9
+
format = pkgs.formats.yaml { };
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/";
29
+
options.services.crowdsec = {
30
+
enable = lib.mkEnableOption "CrowdSec Security Engine";
32
+
package = lib.mkPackageOption pkgs "crowdsec" { };
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";
36
+
openFirewall = lib.mkOption {
37
+
type = lib.types.bool;
41
+
Whether to automatically open firewall ports for `crowdsec`.
45
+
user = lib.mkOption {
46
+
type = lib.types.str;
47
+
description = "The user to run crowdsec as";
48
+
default = "crowdsec";
51
+
group = lib.mkOption {
52
+
type = lib.types.str;
53
+
description = "The group to run crowdsec as";
54
+
default = "crowdsec";
57
+
name = lib.mkOption {
58
+
type = lib.types.str;
60
+
Name of the machine when registering it at the central or local api.
62
+
default = config.networking.hostName;
63
+
defaultText = lib.literalExpression "config.networking.hostName";
66
+
localConfig = lib.mkOption {
67
+
type = lib.types.submodule {
69
+
acquisitions = lib.mkOption {
70
+
type = lib.types.listOf format.type;
73
+
A list of acquisition specifications, which define the data sources you want to be parsed.
75
+
See <https://docs.crowdsec.net/docs/data_sources/intro> for details.
79
+
source = "journalctl";
80
+
journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ];
87
+
scenarios = lib.mkOption {
88
+
type = lib.types.listOf format.type;
91
+
A list of scenarios specifications.
93
+
See <https://docs.crowdsec.net/docs/scenarios/intro> for details.
98
+
name = "crowdsecurity/myservice-bf";
99
+
description = "Detect myservice bruteforce";
100
+
filter = "evt.Meta.log_type == 'myservice_failed_auth'";
103
+
groupby = "evt.Meta.source_ip";
107
+
parsers = lib.mkOption {
108
+
type = lib.types.submodule {
110
+
s00Raw = lib.mkOption {
111
+
type = lib.types.listOf format.type;
114
+
A list of stage s00-raw specifications. Most of the time, those are already included in the hub, but are presented here anyway.
116
+
See <https://docs.crowdsec.net/docs/parsers/intro> for details.
119
+
s01Parse = lib.mkOption {
120
+
type = lib.types.listOf format.type;
123
+
A list of stage s01-parse specifications.
125
+
See <https://docs.crowdsec.net/docs/parsers/intro> for details.
131
+
onsuccess = "next_stage";
132
+
name = "example/custom-service-logs";
133
+
description = "Parsing custom service logs";
135
+
pattern = "^%{DATA:some_data}$";
136
+
apply_on = "message";
140
+
parsed = "is_my_custom_service";
147
+
s02Enrich = lib.mkOption {
148
+
type = lib.types.listOf format.type;
151
+
A list of stage s02-enrich specifications. Inside this list, you can specify Parser Whitelists.
153
+
See <https://docs.crowdsec.net/docs/whitelist/intro> for details.
157
+
name = "myips/whitelist";
158
+
description = "Whitelist parse events from my IPs";
160
+
reason = "My IP ranges";
175
+
postOverflows = lib.mkOption {
176
+
type = lib.types.submodule {
178
+
s01Whitelist = lib.mkOption {
179
+
type = lib.types.listOf format.type;
182
+
A list of stage s01-whitelist specifications. Inside this list, you can specify Postoverflows Whitelists.
184
+
See <https://docs.crowdsec.net/docs/whitelist/intro> for details.
188
+
name = "postoverflows/whitelist_my_dns_domain";
189
+
description = "Whitelist my reverse DNS";
191
+
reason = "Don't ban me";
193
+
"evt.Enriched.reverse_dns endsWith '.local.'"
203
+
contexts = lib.mkOption {
204
+
type = lib.types.listOf format.type;
206
+
A list of additional contexts to specify.
208
+
See <https://docs.crowdsec.net/docs/next/log_processor/alert_context/intro> for details.
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" ];
222
+
notifications = lib.mkOption {
223
+
type = lib.types.listOf format.type;
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.
227
+
See <https://docs.crowdsec.net/docs/notification_plugins/intro> for details.
232
+
name = "default_http_notification";
233
+
log_level = "info";
237
+
url = "https://example.com/hook";
243
+
profiles = lib.mkOption {
244
+
type = lib.types.listOf format.type;
246
+
A list of profiles to enable.
248
+
See <https://docs.crowdsec.net/docs/profiles/intro> for more details.
252
+
name = "default_ip_remediation";
254
+
"Alert.Remediation == true && Alert.GetScope() == 'Ip'"
262
+
on_success = "break";
265
+
name = "default_range_remediation";
267
+
"Alert.Remediation == true && Alert.GetScope() == 'Range'"
275
+
on_success = "break";
280
+
name = "default_ip_remediation";
282
+
"Alert.Remediation == true && Alert.GetScope() == 'Ip'"
290
+
on_success = "break";
293
+
name = "default_range_remediation";
295
+
"Alert.Remediation == true && Alert.GetScope() == 'Range'"
303
+
on_success = "break";
307
+
patterns = lib.mkOption {
308
+
type = lib.types.listOf lib.types.package;
310
+
example = lib.literalExpression ''
311
+
[ (pkgs.writeTextDir "custom_service_logs" (builtins.readFile ./custom_service_logs)) ]
319
+
hub = lib.mkOption {
320
+
type = lib.types.submodule {
322
+
collections = lib.mkOption {
323
+
type = lib.types.listOf lib.types.str;
325
+
description = "List of hub collections to install";
326
+
example = [ "crowdsecurity/linux" ];
329
+
scenarios = lib.mkOption {
330
+
type = lib.types.listOf lib.types.str;
332
+
description = "List of hub scenarios to install";
333
+
example = [ "crowdsecurity/ssh-bf" ];
336
+
parsers = lib.mkOption {
337
+
type = lib.types.listOf lib.types.str;
339
+
description = "List of hub parsers to install";
340
+
example = [ "crowdsecurity/sshd-logs" ];
343
+
postOverflows = lib.mkOption {
344
+
type = lib.types.listOf lib.types.str;
346
+
description = "List of hub postoverflows to install";
347
+
example = [ "crowdsecurity/auditd-nix-wrappers-whitelist-process" ];
350
+
appSecConfigs = lib.mkOption {
351
+
type = lib.types.listOf lib.types.str;
353
+
description = "List of hub appsec configurations to install";
354
+
example = [ "crowdsecurity/appsec-default" ];
357
+
appSecRules = lib.mkOption {
358
+
type = lib.types.listOf lib.types.str;
360
+
description = "List of hub appsec rules to install";
361
+
example = [ "crowdsecurity/base-config" ];
364
+
branch = lib.mkOption {
365
+
type = lib.types.str;
366
+
default = "master";
368
+
The git branch on which cscli is going to fetch configurations.
370
+
See `https://docs.crowdsec.net/docs/configuration/crowdsec_configuration/#hub_branch` for more information.
382
+
Hub collections, parsers, AppSec rules, etc.
386
+
settings = lib.mkOption {
387
+
type = lib.types.submodule {
389
+
general = lib.mkOption {
391
+
Settings for the main CrowdSec configuration file.
393
+
Refer to the defaults at <https://github.com/crowdsecurity/crowdsec/blob/master/config/config.yaml>.
395
+
type = format.type;
398
+
simulation = lib.mkOption {
399
+
type = format.type;
401
+
simulation = false;
404
+
Attributes inside the simulation.yaml file.
408
+
lapi = lib.mkOption {
409
+
type = lib.types.submodule {
411
+
credentialsFile = lib.mkOption {
412
+
type = lib.types.nullOr lib.types.path;
413
+
example = "/run/crowdsec/lapi.yaml";
415
+
The LAPI credential file to use.
422
+
LAPI Configuration attributes
426
+
capi = lib.mkOption {
427
+
type = lib.types.submodule {
429
+
credentialsFile = lib.mkOption {
430
+
type = lib.types.nullOr lib.types.path;
431
+
example = "/run/crowdsec/capi.yaml";
433
+
The CAPI credential file to use.
440
+
CAPI Configuration attributes
444
+
console = lib.mkOption {
445
+
type = lib.types.submodule {
447
+
tokenFile = lib.mkOption {
448
+
type = lib.types.nullOr lib.types.path;
449
+
example = "/run/crowdsec/console_token.yaml";
451
+
The Console Token file to use.
455
+
configuration = lib.mkOption {
456
+
type = format.type;
458
+
share_manual_decisions = false;
459
+
share_custom = false;
460
+
share_tainted = false;
461
+
share_context = false;
464
+
Attributes inside the console.yaml file.
470
+
Console Configuration attributes
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";
487
+
cfg.localConfig.patterns
488
+
"${lib.attrsets.getOutput "out" cfg.package}/share/crowdsec/config/patterns/"
492
+
cscli = pkgs.writeShellScriptBin "cscli" ''
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 ]}"
497
+
if [ "$USER" != "${cfg.user}" ]; then
499
+
if config.security.sudo.enable then
500
+
"sudo='exec ${config.security.wrapperDir}/sudo -u ${cfg.user}'"
502
+
">&2 echo 'Aborting, cscli must be run as user `${cfg.user}`!'; exit 2"
505
+
$sudo ${lib.getExe' cfg.package "cscli"} -c=${configFile} "$@"
508
+
localScenariosMap = (map (format.generate "scenario.yaml") cfg.localConfig.scenarios);
509
+
localParsersS00RawMap = (
510
+
map (format.generate "parsers-s00-raw.yaml") cfg.localConfig.parsers.s00Raw
512
+
localParsersS01ParseMap = (
513
+
map (format.generate "parsers-s01-parse.yaml") cfg.localConfig.parsers.s01Parse
515
+
localParsersS02EnrichMap = (
516
+
map (format.generate "parsers-s02-enrich.yaml") cfg.localConfig.parsers.s02Enrich
518
+
localPostOverflowsS01WhitelistMap = (
519
+
map (format.generate "postoverflows-s01-whitelist.yaml") cfg.localConfig.postOverflows.s01Whitelist
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" ''
525
+
${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.profiles}
528
+
localAcquisisionFile = pkgs.writeText "local_acquisisions.yaml" ''
530
+
${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.acquisitions}
535
+
"set -euo pipefail"
536
+
"${lib.getExe' pkgs.coreutils "mkdir"} -p '${hubDir}'"
537
+
"${lib.getExe cscli} hub update"
539
+
++ lib.optionals (cfg.hub.collections != [ ]) [
540
+
"${lib.getExe cscli} collections install ${
541
+
lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.collections
544
+
++ lib.optionals (cfg.hub.scenarios != [ ]) [
545
+
"${lib.getExe cscli} scenarios install ${
546
+
lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.scenarios
549
+
++ lib.optionals (cfg.hub.parsers != [ ]) [
550
+
"${lib.getExe cscli} parsers install ${
551
+
lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.parsers
554
+
++ lib.optionals (cfg.hub.postOverflows != [ ]) [
555
+
"${lib.getExe cscli} postoverflows install ${
556
+
lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.postOverflows
559
+
++ lib.optionals (cfg.hub.appSecConfigs != [ ]) [
560
+
"${lib.getExe cscli} appsec-configs install ${
561
+
lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecConfigs
564
+
++ lib.optionals (cfg.hub.appSecRules != [ ]) [
565
+
"${lib.getExe cscli} appsec-rules install ${
566
+
lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecRules
569
+
++ lib.optionals (cfg.settings.general.api.server.enable) [
571
+
if [ ! -s "${cfg.settings.general.api.client.credentials_path}" ]; then
572
+
${lib.getExe cscli} machine add "${cfg.name}" --auto
576
+
++ lib.optionals (cfg.settings.capi.credentialsFile != null) [
578
+
if ! grep -q password "${cfg.settings.capi.credentialsFile}" ]; then
579
+
${lib.getExe cscli} capi register
583
+
++ lib.optionals (cfg.settings.console.tokenFile != null) [
585
+
if [ ! -e "${cfg.settings.console.tokenFile}" ]; then
586
+
${lib.getExe cscli} console enroll "$(cat ${cfg.settings.console.tokenFile})" --name ${cfg.name}
591
+
setupScript = pkgs.writeShellScriptBin "crowdsec-setup" (
592
+
lib.strings.concatStringsSep "\n" scriptArray
596
+
lib.mkIf (cfg.enable) {
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."
603
+
++ lib.optionals (cfg.localConfig.acquisitions == [ ]) [
604
+
"By not specifying acquisitions in services.crowdsec.localConfig.acquisitions, CrowdSec will not look for any data source."
607
+
services.crowdsec.settings.general = {
610
+
log_media = "stdout";
613
+
config_dir = confDir;
614
+
data_dir = stateDir;
615
+
simulation_path = simulationFile;
617
+
index_path = lib.strings.normalizePath "${stateDir}/hub/.index.json";
618
+
notification_dir = notificationsDir;
619
+
plugin_dir = pluginDir;
620
+
pattern_dir = patternsDir;
623
+
type = lib.mkDefault "sqlite";
624
+
db_path = lib.mkDefault (lib.strings.normalizePath "${stateDir}/crowdsec.db");
625
+
use_wal = lib.mkDefault true;
627
+
crowdsec_service = {
628
+
enable = lib.mkDefault true;
629
+
acquisition_path = lib.mkDefault localAcquisisionFile;
633
+
credentials_path = cfg.settings.lapi.credentialsFile;
636
+
enable = lib.mkDefault false;
637
+
listen_uri = lib.mkDefault "127.0.0.1:8080";
639
+
console_path = lib.mkDefault consoleFile;
640
+
profiles_path = lib.mkDefault localProfilesFile;
642
+
online_client = lib.mkDefault {
643
+
sharing = lib.mkDefault true;
644
+
pull = lib.mkDefault {
645
+
community = lib.mkDefault true;
646
+
blocklists = lib.mkDefault true;
648
+
credentials_path = cfg.settings.capi.credentialsFile;
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;
659
+
hub_branch = cfg.hub.branch;
664
+
systemPackages = [ cscli ];
667
+
systemd.packages = [ cfg.package ];
669
+
systemd.timers.crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) {
670
+
description = "Update the crowdsec hub index";
671
+
wantedBy = [ "timers.target" ];
673
+
OnCalendar = "daily";
674
+
RandomizedDelaySec = 300;
675
+
Persistent = "yes";
676
+
Unit = "crowdsec-update-hub.service";
679
+
systemd.services = {
680
+
crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) {
681
+
description = "Update the crowdsec hub index";
686
+
LimitNOFILE = 65536;
687
+
NoNewPrivileges = true;
688
+
LockPersonality = true;
694
+
ProtectSystem = "strict";
695
+
PrivateUsers = true;
696
+
ProtectHome = true;
698
+
PrivateDevices = true;
699
+
ProtectHostname = true;
701
+
ProtectKernelTunables = true;
702
+
ProtectKernelModules = true;
703
+
ProtectControlGroups = true;
704
+
ProtectProc = "invisible";
705
+
SystemCallFilter = [
706
+
" " # This is needed to clear the SystemCallFilter existing definitions
719
+
CapabilityBoundingSet = [
720
+
" " # Reset all capabilities to an empty set
722
+
RestrictAddressFamilies = [
723
+
" " # This is needed to clear the RestrictAddressFamilies existing definitions
724
+
"none" # Remove all addresses families
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;
742
+
description = "CrowdSec agent";
743
+
wantedBy = [ "multi-user.target" ];
744
+
after = [ "network-online.target" ];
745
+
wants = [ "network-online.target" ];
746
+
path = lib.mkForce [ ];
756
+
LimitNOFILE = 65536;
757
+
NoNewPrivileges = true;
758
+
LockPersonality = true;
764
+
ProtectSystem = "strict";
765
+
PrivateUsers = true;
766
+
ProtectHome = true;
768
+
PrivateDevices = true;
769
+
ProtectHostname = true;
770
+
ProtectClock = true;
772
+
ProtectKernelTunables = true;
773
+
ProtectKernelModules = true;
774
+
ProtectControlGroups = true;
775
+
ProtectProc = "invisible";
776
+
SystemCallFilter = [
777
+
" " # This is needed to clear the SystemCallFilter existing definitions
790
+
CapabilityBoundingSet = [
791
+
" " # Reset all capabilities to an empty set
792
+
"CAP_SYSLOG" # Add capability to read syslog
794
+
RestrictAddressFamilies = [
795
+
" " # This is needed to clear the RestrictAddressFamilies existing definitions
796
+
"none" # Remove all addresses families
801
+
DevicePolicy = "closed";
802
+
ProtectKernelLogs = true;
803
+
SystemCallArchitectures = "native";
804
+
DynamicUser = true;
805
+
RestrictNamespaces = true;
806
+
RestrictRealtime = true;
807
+
RestrictSUIDSGID = true;
809
+
" " # This is needed to clear the ExecReload definitions from upstream
812
+
" " # This is needed to clear the ExecStart definitions from upstream
813
+
"${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -info"
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"
824
+
systemd.tmpfiles.settings = {
827
+
builtins.listToAttrs (
831
+
name = lib.strings.normalizePath dirName;
845
+
localPostOverflowsDir
846
+
localPostOverflowsS01WhitelistDir
848
+
localParsersS00RawDir
849
+
localParsersS01ParseDir
850
+
localParsersS02EnrichDir
856
+
// builtins.listToAttrs (
857
+
map (scenarioFile: {
859
+
name = lib.strings.normalizePath "${localScenariosDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf scenarioFile)}";
863
+
argument = "${scenarioFile}";
866
+
}) localScenariosMap
868
+
// builtins.listToAttrs (
871
+
name = lib.strings.normalizePath "${localParsersS00RawDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}";
875
+
argument = "${parser}";
878
+
}) localParsersS00RawMap
880
+
// builtins.listToAttrs (
883
+
name = lib.strings.normalizePath "${localParsersS01ParseDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}";
887
+
argument = "${parser}";
890
+
}) localParsersS01ParseMap
892
+
// builtins.listToAttrs (
895
+
name = lib.strings.normalizePath "${localParsersS02EnrichDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}";
899
+
argument = "${parser}";
902
+
}) localParsersS02EnrichMap
904
+
// builtins.listToAttrs (
905
+
map (postoverflow: {
907
+
name = lib.strings.normalizePath "${localPostOverflowsS01WhitelistDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf postoverflow)}";
911
+
argument = "${postoverflow}";
914
+
}) localPostOverflowsS01WhitelistMap
916
+
// builtins.listToAttrs (
919
+
name = lib.strings.normalizePath "${localContextsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf context)}";
923
+
argument = "${context}";
926
+
}) localContextsMap
928
+
// builtins.listToAttrs (
929
+
map (notification: {
931
+
name = lib.strings.normalizePath "${notificationsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf notification)}";
935
+
argument = "${notification}";
938
+
}) localNotificationsMap
942
+
users.users.${cfg.user} = {
944
+
description = lib.mkDefault "CrowdSec service user";
945
+
isSystemUser = true;
947
+
extraGroups = [ "systemd-journal" ];
950
+
users.groups.${cfg.group} = lib.mapAttrs (name: lib.mkDefault) { };
952
+
networking.firewall.allowedTCPPorts =
954
+
parsePortFromURLOption =
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))
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")
967
+
maintainers = with lib.maintainers; [