From 188fe4c1dcc5bed14c11b47055f3f767e48c24c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Barnouin?= Date: Wed, 28 May 2025 10:52:19 +0200 Subject: [PATCH 1/2] Add capi cred file --- modules/crowdsec.nix | 16 +- modules/crowdsec2.nix | 924 +++++++++++++++++++++++++ secrets.nix | 1 + services/nginx/default.nix | 5 + services/nginx/secrets/cs-capi-key.age | 16 + 5 files changed, 954 insertions(+), 8 deletions(-) create mode 100644 modules/crowdsec2.nix create mode 100644 services/nginx/secrets/cs-capi-key.age diff --git a/modules/crowdsec.nix b/modules/crowdsec.nix index 76d9999..fd9f702 100644 --- a/modules/crowdsec.nix +++ b/modules/crowdsec.nix @@ -602,14 +602,14 @@ in console_path = mkDefault consoleFile; profiles_path = mkDefault localProfilesFile; - #online_client = mkDefault { - # sharing = mkDefault true; - # pull = mkDefault { - # community = mkDefault true; - # blocklists = mkDefault true; - # }; - # credentials_path = cfg.settings.capi.credentialsFile; - #}; + online_client = mkDefault { + sharing = mkDefault true; + pull = mkDefault { + community = mkDefault true; + blocklists = mkDefault true; + }; + credentials_path = cfg.settings.capi.credentialsFile; + }; }; }; prometheus = { diff --git a/modules/crowdsec2.nix b/modules/crowdsec2.nix new file mode 100644 index 0000000..f7fb8f8 --- /dev/null +++ b/modules/crowdsec2.nix @@ -0,0 +1,924 @@ +{ + config, + pkgs, + lib, + ... +}: +let + + format = pkgs.formats.yaml { }; + + rootDir = "/var/lib/crowdsec"; + stateDir = "${rootDir}/state"; + confDir = "/etc/crowdsec/"; + hubDir = "${stateDir}/hub/"; + notificationsDir = "${confDir}/notifications/"; + pluginDir = "${confDir}/plugins/"; + parsersDir = "${confDir}/parsers/"; + localPostOverflowsDir = "${confDir}/postoverflows/"; + localPostOverflowsS01WhitelistDir = "${localPostOverflowsDir}/s01-whitelist/"; + localScenariosDir = "${confDir}/scenarios/"; + localParsersS00RawDir = "${parsersDir}/s00-raw/"; + localParsersS01ParseDir = "${parsersDir}/s01-parse/"; + localParsersS02EnrichDir = "${parsersDir}/s02-enrich/"; + localContextsDir = "${confDir}/contexts/"; + +in +{ + + options.services.crowdsec = with lib; { + enable = mkEnableOption "CrowdSec Security Engine"; + + package = mkPackageOption pkgs "crowdsec" { }; + + autoUpdateService = mkEnableOption "Auto Hub Update"; + + openFirewall = mkEnableOption "opening the ports in the firewall"; + + user = mkOption { + type = types.str; + description = "The user to run crowdsec as"; + default = "crowdsec"; + }; + + group = mkOption { + type = types.str; + description = "The group to run crowdsec as"; + default = "crowdsec"; + }; + + name = mkOption { + type = types.str; + description = '' + Name of the machine when registering it at the central or local api. + ''; + default = config.networking.hostName; + defaultText = lib.literalExpression "config.networking.hostName"; + }; + + localConfig = mkOption { + type = types.submodule { + options = { + acquisitions = mkOption { + type = types.listOf format.type; + default = [ ]; + description = '' + A list of acquisition specifications, which define the data sources you want to be parsed. + See for details. + ''; + example = [ + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; + labels = { + type = "syslog"; + }; + } + ]; + }; + scenarios = mkOption { + type = types.listOf format.type; + default = [ ]; + description = '' + A list of scenarios specifications. + See for details. + ''; + example = [ + { + type = "leaky"; + name = "crowdsecurity/myservice-bf"; + description = "Detect myservice bruteforce"; + filter = "evt.Meta.log_type == 'myservice_failed_auth'"; + leakspeed = "10s"; + capacity = 5; + groupby = "evt.Meta.source_ip"; + } + ]; + }; + parsers = mkOption { + type = types.submodule { + options = { + s00Raw = mkOption { + type = types.listOf format.type; + default = [ ]; + description = '' + A list of stage s00-raw specifications. Most of the time, those are already included in the hub, but are presented here anyway. + See for details. + ''; + }; + s01Parse = mkOption { + type = types.listOf format.type; + default = [ ]; + description = '' + A list of stage s01-parse specifications. + See for details. + ''; + example = [ + { + filter = "1=1"; + debug = true; + onsuccess = "next_stage"; + name = "example/custom-service-logs"; + description = "Parsing custom service logs"; + grok = { + pattern = "^%{DATA:some_data}$"; + apply_on = "message"; + }; + statics = [ + { + parsed = "is_my_custom_service"; + value = "yes"; + } + ]; + } + ]; + }; + s02Enrich = mkOption { + type = types.listOf format.type; + default = [ ]; + description = '' + A list of stage s02-enrich specifications. Inside this list, you can specify Parser Whitelists. + See for details. + ''; + example = [ + { + name = "myips/whitelist"; + description = "Whitelist parse events from my IPs"; + whitelist = { + reason = "My IP ranges"; + ip = [ + "1.2.3.4" + ]; + cidr = [ + "1.2.3.0/24" + ]; + }; + } + ]; + }; + }; + }; + default = { }; + }; + postOverflows = mkOption { + type = types.submodule { + options = { + s01Whitelist = mkOption { + type = types.listOf format.type; + default = [ ]; + description = '' + A list of stage s01-whitelist specifications. Inside this list, you can specify Postoverflows Whitelists. + See for details. + ''; + example = [ + { + name = "postoverflows/whitelist_my_dns_domain"; + description = "Whitelist my reverse DNS"; + whitelist = { + reason = "Don't ban me"; + expression = [ + "evt.Enriched.reverse_dns endsWith '.local.'" + ]; + }; + } + ]; + }; + }; + }; + default = { }; + }; + contexts = mkOption { + type = types.listOf format.type; + description = '' + A list of additional contexts to specify. + See for details. + ''; + example = [ + { + context = { + target_uri = [ "evt.Meta.http_path" ]; + user_agent = [ "evt.Meta.http_user_agent" ]; + method = [ "evt.Meta.http_verb" ]; + status = [ "evt.Meta.http_status" ]; + }; + } + ]; + default = [ ]; + }; + notifications = mkOption { + type = types.listOf format.type; + description = '' + 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. + See for details. + ''; + example = [ + { + type = "http"; + name = "default_http_notification"; + log_level = "info"; + format = '' + {{.|toJson}} + ''; + url = "https://example.com/hook"; + method = "POST"; + } + ]; + default = [ ]; + }; + profiles = mkOption { + type = types.listOf format.type; + description = '' + A list of profiles to enable. + See for more details. + ''; + example = [ + { + name = "default_ip_remediation"; + filters = [ + "Alert.Remediation == true && Alert.GetScope() == 'Ip'" + ]; + decisions = [ + { + type = "ban"; + duration = "4h"; + } + ]; + on_success = "break"; + } + { + name = "default_range_remediation"; + filters = [ + "Alert.Remediation == true && Alert.GetScope() == 'Range'" + ]; + decisions = [ + { + type = "ban"; + duration = "4h"; + } + ]; + on_success = "break"; + } + ]; + default = [ + { + name = "default_ip_remediation"; + filters = [ + "Alert.Remediation == true && Alert.GetScope() == 'Ip'" + ]; + decisions = [ + { + type = "ban"; + duration = "4h"; + } + ]; + on_success = "break"; + } + { + name = "default_range_remediation"; + filters = [ + "Alert.Remediation == true && Alert.GetScope() == 'Range'" + ]; + decisions = [ + { + type = "ban"; + duration = "4h"; + } + ]; + on_success = "break"; + } + ]; + }; + patterns = mkOption { + type = types.listOf types.package; + default = [ ]; + example = lib.literalExpression '' + [ (pkgs.writeTextDir "custom_service_logs" (builtins.readFile ./custom_service_logs)) ] + ''; + }; + }; + }; + default = { }; + }; + + hub = mkOption { + type = types.submodule { + options = { + collections = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hub collections to install"; + example = [ "crowdsecurity/linux" ]; + }; + + scenarios = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hub scenarios to install"; + example = [ "crowdsecurity/ssh-bf" ]; + }; + + parsers = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hub parsers to install"; + example = [ "crowdsecurity/sshd-logs" ]; + }; + + postOverflows = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hub postoverflows to install"; + example = [ "crowdsecurity/auditd-nix-wrappers-whitelist-process" ]; + }; + + appSecConfigs = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hub appsec configurations to install"; + example = [ "crowdsecurity/appsec-default" ]; + }; + + appSecRules = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hub appsec rules to install"; + example = [ "crowdsecurity/base-config" ]; + }; + }; + }; + default = { }; + description = '' + Hub collections, parsers, AppSec rules, etc. + ''; + }; + + settings = mkOption { + type = types.submodule { + options = { + general = mkOption { + description = '' + Settings for the main CrowdSec configuration file. + Refer to the defaults at . + ''; + type = format.type; + default = { }; + }; + simulation = mkOption { + type = format.type; + default = { + simulation = false; + }; + description = '' + Attributes inside the simulation.yaml file. + ''; + }; + + lapi = mkOption { + type = types.submodule { + options = { + credentialsFile = mkOption { + type = types.nullOr types.path; + example = "/run/crowdsec/lapi.yaml"; + description = '' + The LAPI credential file to use. + ''; + default = null; + }; + }; + }; + description = '' + LAPI Configuration attributes + ''; + default = { }; + }; + capi = mkOption { + type = types.submodule { + options = { + credentialsFile = mkOption { + type = types.nullOr types.path; + example = "/run/crowdsec/capi.yaml"; + description = '' + The CAPI credential file to use. + ''; + default = null; + }; + }; + }; + description = '' + CAPI Configuration attributes + ''; + default = { }; + }; + console = mkOption { + type = types.submodule { + options = { + tokenFile = mkOption { + type = types.nullOr types.path; + example = "/run/crowdsec/console_token.yaml"; + description = '' + The Console Token file to use. + ''; + default = null; + }; + configuration = mkOption { + type = format.type; + default = { + share_manual_decisions = false; + share_custom = false; + share_tainted = false; + share_context = false; + }; + description = '' + Attributes inside the console.yaml file. + ''; + }; + }; + }; + description = '' + Console Configuration attributes + ''; + default = { }; + }; + }; + }; + }; + }; + config = + let + cfg = config.services.crowdsec; + configFile = format.generate "crowdsec.yaml" cfg.settings.general; + simulationFile = format.generate "simulation.yaml" cfg.settings.simulation; + consoleFile = format.generate "console.yaml" cfg.settings.console.configuration; + patternsDir = pkgs.buildPackages.symlinkJoin { + name = "crowdsec-patterns"; + paths = [ + cfg.localConfig.patterns + "${lib.attrsets.getOutput "out" cfg.package}/share/crowdsec/config/patterns/" + ]; + }; + + cscli = pkgs.writeShellScriptBin "cscli" '' + set -euo pipefail + # cscli needs crowdsec on it's path in order to be able to run `cscli explain` + export PATH="$PATH:${lib.makeBinPath [ cfg.package ]}" + sudo=exec + if [ "$USER" != "${cfg.user}" ]; then + sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}' + fi + $sudo ${lib.getExe' cfg.package "cscli"} -c=${configFile} "$@" + ''; + + localScenariosMap = (map (format.generate "scenario.yaml") cfg.localConfig.scenarios); + localParsersS00RawMap = ( + map (format.generate "parsers-s00-raw.yaml") cfg.localConfig.parsers.s00Raw + ); + localParsersS01ParseMap = ( + map (format.generate "parsers-s01-parse.yaml") cfg.localConfig.parsers.s01Parse + ); + localParsersS02EnrichMap = ( + map (format.generate "parsers-s02-enrich.yaml") cfg.localConfig.parsers.s02Enrich + ); + localPostOverflowsS01WhitelistMap = ( + map (format.generate "postoverflows-s01-whitelist.yaml") cfg.localConfig.postOverflows.s01Whitelist + ); + localContextsMap = (map (format.generate "context.yaml") cfg.localConfig.contexts); + localNotificationsMap = (map (format.generate "notification.yaml") cfg.localConfig.notifications); + localProfilesFile = pkgs.writeText "local_profiles.yaml" '' + --- + ${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.profiles} + --- + ''; + localAcquisisionFile = pkgs.writeText "local_acquisisions.yaml" '' + --- + ${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.acquisitions} + --- + ''; + + scriptArray = + [ + "set -euo pipefail" + "${lib.getExe cscli} hub update" + ] + ++ lib.optionals (cfg.hub.collections != [ ]) [ + "${lib.getExe cscli} collections install ${ + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.collections + }" + ] + ++ lib.optionals (cfg.hub.scenarios != [ ]) [ + "${lib.getExe cscli} scenarios install ${ + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.scenarios + }" + ] + ++ lib.optionals (cfg.hub.parsers != [ ]) [ + "${lib.getExe cscli} parsers install ${ + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.parsers + }" + ] + ++ lib.optionals (cfg.hub.postOverflows != [ ]) [ + "${lib.getExe cscli} postoverflows install ${ + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.postOverflows + }" + ] + ++ lib.optionals (cfg.hub.appSecConfigs != [ ]) [ + "${lib.getExe cscli} appsec-configs install ${ + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecConfigs + }" + ] + ++ lib.optionals (cfg.hub.appSecRules != [ ]) [ + "${lib.getExe cscli} appsec-rules install ${ + lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecRules + }" + ] + ++ lib.optionals (cfg.settings.general.api.server.enable) [ + '' + if [ ! -s "${cfg.settings.general.api.client.credentials_path}" ]; then + ${lib.getExe cscli} machine add "${cfg.name}" --auto + fi + '' + ] + ++ lib.optionals (cfg.settings.capi.credentialsFile != null) [ + '' + if ! grep -q password "${cfg.settings.capi.credentialsFile}" ]; then + ${lib.getExe cscli} capi register + fi + '' + ] + ++ lib.optionals (cfg.settings.console.tokenFile != null) [ + '' + if [ ! -e "${cfg.settings.console.tokenFile}" ]; then + ${lib.getExe cscli} console enroll "$(cat ${cfg.settings.console.tokenFile})" --name ${cfg.name} + fi + '' + ]; + + setupScript = pkgs.writeShellScriptBin "crowdsec-setup" ( + lib.strings.concatStringsSep "\n" scriptArray + ); + + in + lib.mkIf (cfg.enable) { + + warnings = + [ ] + ++ lib.optionals (cfg.localConfig.profiles == [ ]) [ + "By not specifying profiles in services.crowdsec.localConfig.profiles, CrowdSec will not react to any alert by default." + ] + ++ lib.optionals (cfg.localConfig.acquisitions == [ ]) [ + "By not specifying acquisitions in services.crowdsec.localConfig.acquisitions, CrowdSec will not look for any data source." + ]; + + services.crowdsec.settings.general = with lib; { + common = { + daemonize = false; + log_media = "stdout"; + }; + config_paths = { + config_dir = confDir; + data_dir = stateDir; + simulation_path = simulationFile; + hub_dir = hubDir; + index_path = lib.strings.normalizePath "${stateDir}/hub/.index.json"; + notification_dir = notificationsDir; + plugin_dir = pluginDir; + pattern_dir = patternsDir; + }; + db_config = { + type = mkDefault "sqlite"; + db_path = mkDefault (lib.strings.normalizePath "${stateDir}/crowdsec.db"); + use_wal = mkDefault true; + }; + crowdsec_service = { + enable = mkDefault true; + acquisition_path = mkDefault localAcquisisionFile; + }; + api = { + client = { + credentials_path = cfg.settings.lapi.credentialsFile; + }; + server = { + enable = mkDefault false; + listen_uri = mkDefault "127.0.0.1:8080"; + + console_path = mkDefault consoleFile; + profiles_path = mkDefault localProfilesFile; + + online_client = mkDefault { + sharing = mkDefault true; + pull = mkDefault { + community = mkDefault true; + blocklists = mkDefault true; + }; + credentials_path = cfg.settings.capi.credentialsFile; + }; + }; + }; + prometheus = { + enabled = mkDefault true; + level = mkDefault "full"; + listen_addr = mkDefault "127.0.0.1"; + listen_port = mkDefault 6060; + }; + cscli = { + hub_branch = "v${cfg.package.version}"; + }; + }; + + environment = { + systemPackages = [ cscli ]; + }; + + systemd.packages = [ cfg.package ]; + + systemd.timers.crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) { + description = "Update the crowdsec hub index"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "daily"; + Persistent = "yes"; + Unit = "crowdsec-update-hub.service"; + }; + }; + systemd.services = { + crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) { + description = "Update the crowdsec hub index"; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + LimitNOFILE = 65536; + NoNewPrivileges = true; + LockPersonality = true; + RemoveIPC = true; + ReadWritePaths = [ + rootDir + confDir + ]; + ProtectSystem = "strict"; + PrivateUsers = true; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + UMask = "0077"; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + ProtectProc = "invisible"; + SystemCallFilter = [ + " " # This is needed to clear the SystemCallFilter existing definitions + "~@reboot" + "~@swap" + "~@obsolete" + "~@mount" + "~@module" + "~@debug" + "~@cpu-emulation" + "~@clock" + "~@raw-io" + "~@privileged" + "~@resources" + ]; + CapabilityBoundingSet = [ + " " # Reset all capabilities to an empty set + ]; + RestrictAddressFamilies = [ + " " # This is needed to clear the RestrictAddressFamilies existing definitions + "none" # Remove all addresses families + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + DevicePolicy = "closed"; + ProtectKernelLogs = true; + SystemCallArchitectures = "native"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + ExecStart = "${lib.getExe cscli} --error hub update"; + ExecStartPost = "systemctl reload crowdsec.service"; + }; + }; + + crowdsec = { + description = "CrowdSec is a free, modern & collaborative behavior detection engine, coupled with a global IP reputation network."; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + path = lib.mkForce [ ]; + environment = { + LC_ALL = "C"; + LANG = "C"; + }; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + Type = "simple"; + RestartSec = 60; + LimitNOFILE = 65536; + NoNewPrivileges = true; + LockPersonality = true; + RemoveIPC = true; + ReadWritePaths = [ + rootDir + confDir + ]; + ProtectSystem = "strict"; + PrivateUsers = true; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectClock = true; + UMask = "0077"; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + ProtectProc = "invisible"; + SystemCallFilter = [ + " " # This is needed to clear the SystemCallFilter existing definitions + "~@reboot" + "~@swap" + "~@obsolete" + "~@mount" + "~@module" + "~@debug" + "~@cpu-emulation" + "~@clock" + "~@raw-io" + "~@privileged" + "~@resources" + ]; + CapabilityBoundingSet = [ + " " # Reset all capabilities to an empty set + "CAP_SYSLOG" # Add capability to read syslog + ]; + RestrictAddressFamilies = [ + " " # This is needed to clear the RestrictAddressFamilies existing definitions + "none" # Remove all addresses families + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + DevicePolicy = "closed"; + ProtectKernelLogs = true; + SystemCallArchitectures = "native"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + ExecReload = [ + " " # This is needed to clear the ExecReload definitions from upstream + ]; + ExecStart = [ + " " # This is needed to clear the ExecStart definitions from upstream + "${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -info" + ]; + ExecStartPre = [ + " " # This is needed to clear the ExecStartPre definitions from upstream + "${lib.getExe setupScript}" + "${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -t -error" + ]; + }; + }; + }; + + systemd.tmpfiles.settings = { + "10-crowdsec" = + builtins.listToAttrs ( + map + (dirName: { + inherit cfg; + name = lib.strings.normalizePath dirName; + value = { + d = { + user = cfg.user; + group = cfg.group; + mode = "0750"; + }; + }; + }) + [ + stateDir + hubDir + confDir + localScenariosDir + localPostOverflowsDir + localPostOverflowsS01WhitelistDir + parsersDir + localParsersS00RawDir + localParsersS01ParseDir + localParsersS02EnrichDir + localContextsDir + notificationsDir + pluginDir + ] + ) + // builtins.listToAttrs ( + map (scenarioFile: { + inherit cfg; + name = lib.strings.normalizePath "${localScenariosDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf scenarioFile)}"; + value = { + link = { + type = "L+"; + argument = "${scenarioFile}"; + }; + }; + }) localScenariosMap + ) + // builtins.listToAttrs ( + map (parser: { + inherit cfg; + name = lib.strings.normalizePath "${localParsersS00RawDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; + value = { + link = { + type = "L+"; + argument = "${parser}"; + }; + }; + }) localParsersS00RawMap + ) + // builtins.listToAttrs ( + map (parser: { + inherit cfg; + name = lib.strings.normalizePath "${localParsersS01ParseDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; + value = { + link = { + type = "L+"; + argument = "${parser}"; + }; + }; + }) localParsersS01ParseMap + ) + // builtins.listToAttrs ( + map (parser: { + inherit cfg; + name = lib.strings.normalizePath "${localParsersS02EnrichDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; + value = { + link = { + type = "L+"; + argument = "${parser}"; + }; + }; + }) localParsersS02EnrichMap + ) + // builtins.listToAttrs ( + map (postoverflow: { + inherit cfg; + name = lib.strings.normalizePath "${localPostOverflowsS01WhitelistDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf postoverflow)}"; + value = { + link = { + type = "L+"; + argument = "${postoverflow}"; + }; + }; + }) localPostOverflowsS01WhitelistMap + ) + // builtins.listToAttrs ( + map (context: { + inherit cfg; + name = lib.strings.normalizePath "${localContextsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf context)}"; + value = { + link = { + type = "L+"; + argument = "${context}"; + }; + }; + }) localContextsMap + ) + // builtins.listToAttrs ( + map (notification: { + inherit cfg; + name = lib.strings.normalizePath "${notificationsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf notification)}"; + value = { + link = { + type = "L+"; + argument = "${notification}"; + }; + }; + }) localNotificationsMap + ); + }; + + users.users.${cfg.user} = { + name = cfg.user; + description = lib.mkDefault "CrowdSec service user"; + isSystemUser = true; + group = cfg.group; + extraGroups = [ "systemd-journal" ]; + }; + + users.groups.${cfg.group} = lib.mapAttrs (name: lib.mkDefault) { }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ + 6060 + 8080 + ]; + }; + + meta = { + maintainers = with lib.maintainers; [ + m0ustach3 + jk + ]; + }; +} + diff --git a/secrets.nix b/secrets.nix index f774c4c..07762b7 100644 --- a/secrets.nix +++ b/secrets.nix @@ -35,6 +35,7 @@ in { "secrets/postgresql-lapi-key.age".publicKeys = [tbarnouin postgresql]; "services/nginx/secrets/cs-lapi-key.age".publicKeys = [tbarnouin nginx]; + "services/nginx/secrets/cs-capi-key.age".publicKeys = [tbarnouin nginx]; "services/minimalConfig/secrets/cs-lapi-key.age".publicKeys = users ++ systems; "secrets/cs-lapi-key.age".publicKeys = users ++ systems; diff --git a/services/nginx/default.nix b/services/nginx/default.nix index cc5d1b3..f390c99 100644 --- a/services/nginx/default.nix +++ b/services/nginx/default.nix @@ -26,6 +26,10 @@ in { file = ./secrets/cs-lapi-key.age; owner = "crowdsec"; }; + age.secrets.nginx-capi-key = { + file = ./secrets/cs-capi-key.age; + owner = "crowdsec"; + }; services = { crowdsec-firewall-bouncer = { enable = true; @@ -42,6 +46,7 @@ in { listen_uri = "${cfg.proxy_ip}:8080"; }; lapi.credentialsFile = "${config.age.secrets.nginx-lapi-key.path}"; + capi.credentialsFile = "${config.age.secrets.nginx-capi-key.path}"; }; hub.collections = [ "firix/authentik" diff --git a/services/nginx/secrets/cs-capi-key.age b/services/nginx/secrets/cs-capi-key.age new file mode 100644 index 0000000..d9c4fe9 --- /dev/null +++ b/services/nginx/secrets/cs-capi-key.age @@ -0,0 +1,16 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE9Xa1ZYdyB0Q1Js +Kys0clVVOFQwVTUzV04yZ0cyUGM5QnJ6L3ZYNTZpZFVKeTRuZWtBCmYyV1RQR2xC +SExUeEx3MjYwOGgzU0s2U1gxV0Z0ajJ2RUE2bE9LQzFZWTgKLT4gc3NoLWVkMjU1 +MTkgbXUwZm5BIEZRKzkxZnRvZThpSm9PUjc0U2VxU015RzRFWmQ5VVBiV0R3UHVJ +aDRuWEEKTXJFWnhRckZuNUVHeXdQMWxLZ1NpUEE4MktYdHhTT0dVbVJUK3NGOTZa +VQotPiAwUH1nLWdyZWFzZSBCdVZcJyZjaCBQXyFvUndDdCBMbk5mOWlUYyBVewoy +cHRlR1JvUEF2VGZ2SnRIaS9RTmJlVWVTd1dneDdGQjh5LzQxSnRTNSs4WmNRWWc5 +WkFlNGlDMWw2NGdtOGpnCkNtTERzb24wSlVRT3pGSWZab2ZxQytCWlk2RTJ3MEhi +YzlOMTBmemFtYzJPRk80NldoRTY5MmJwbHhGUAotLS0gdHBOSHRxOXNtMmo1QnR5 +N1c0clUxRnlKL2l5bU42ejVRQ0J1dEh2RzY5MAp+G3SFYm9lPhGr3CjIU11K8a8s +YIBgWVCSZ57Dr5LPKfy7kIWuJTFI/YAx1qN4fjwwEXuvyNVWPPVOeO1PAaLhHgAt +1Tx1V3u/LqyzYeXQWuazBZgHbLU+weKoBSmP8JCWXAyVOPzaQdpEDCT+hB/OFYdc +i7lKvTwttJK4Fvv2bVE6R1q1mRCiIz+sVLVydXVQHEqSgGP6o8L9cCfccoIDk3Oo +mtrP+ZEaJU9j4ZM/Y+Shsw/U2TBXOk18f9g+CidhCw== +-----END AGE ENCRYPTED FILE----- From 313913758a6a65497e3b208ca4737d9b15cc2209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Barnouin?= Date: Wed, 28 May 2025 11:48:38 +0200 Subject: [PATCH 2/2] Cleanup and pin crowdsec pkg to 1.6.4 (URL bug) --- modules/crowdsec2.nix | 924 ----------------------------------- services/nginx/default.nix | 1 + services/nixarr/default.nix | 29 +- systems/minimalLXCConfig.nix | 22 +- systems/minimalVMConfig.nix | 23 +- 5 files changed, 64 insertions(+), 935 deletions(-) delete mode 100644 modules/crowdsec2.nix diff --git a/modules/crowdsec2.nix b/modules/crowdsec2.nix deleted file mode 100644 index f7fb8f8..0000000 --- a/modules/crowdsec2.nix +++ /dev/null @@ -1,924 +0,0 @@ -{ - config, - pkgs, - lib, - ... -}: -let - - format = pkgs.formats.yaml { }; - - rootDir = "/var/lib/crowdsec"; - stateDir = "${rootDir}/state"; - confDir = "/etc/crowdsec/"; - hubDir = "${stateDir}/hub/"; - notificationsDir = "${confDir}/notifications/"; - pluginDir = "${confDir}/plugins/"; - parsersDir = "${confDir}/parsers/"; - localPostOverflowsDir = "${confDir}/postoverflows/"; - localPostOverflowsS01WhitelistDir = "${localPostOverflowsDir}/s01-whitelist/"; - localScenariosDir = "${confDir}/scenarios/"; - localParsersS00RawDir = "${parsersDir}/s00-raw/"; - localParsersS01ParseDir = "${parsersDir}/s01-parse/"; - localParsersS02EnrichDir = "${parsersDir}/s02-enrich/"; - localContextsDir = "${confDir}/contexts/"; - -in -{ - - options.services.crowdsec = with lib; { - enable = mkEnableOption "CrowdSec Security Engine"; - - package = mkPackageOption pkgs "crowdsec" { }; - - autoUpdateService = mkEnableOption "Auto Hub Update"; - - openFirewall = mkEnableOption "opening the ports in the firewall"; - - user = mkOption { - type = types.str; - description = "The user to run crowdsec as"; - default = "crowdsec"; - }; - - group = mkOption { - type = types.str; - description = "The group to run crowdsec as"; - default = "crowdsec"; - }; - - name = mkOption { - type = types.str; - description = '' - Name of the machine when registering it at the central or local api. - ''; - default = config.networking.hostName; - defaultText = lib.literalExpression "config.networking.hostName"; - }; - - localConfig = mkOption { - type = types.submodule { - options = { - acquisitions = mkOption { - type = types.listOf format.type; - default = [ ]; - description = '' - A list of acquisition specifications, which define the data sources you want to be parsed. - See for details. - ''; - example = [ - { - source = "journalctl"; - journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; - labels = { - type = "syslog"; - }; - } - ]; - }; - scenarios = mkOption { - type = types.listOf format.type; - default = [ ]; - description = '' - A list of scenarios specifications. - See for details. - ''; - example = [ - { - type = "leaky"; - name = "crowdsecurity/myservice-bf"; - description = "Detect myservice bruteforce"; - filter = "evt.Meta.log_type == 'myservice_failed_auth'"; - leakspeed = "10s"; - capacity = 5; - groupby = "evt.Meta.source_ip"; - } - ]; - }; - parsers = mkOption { - type = types.submodule { - options = { - s00Raw = mkOption { - type = types.listOf format.type; - default = [ ]; - description = '' - A list of stage s00-raw specifications. Most of the time, those are already included in the hub, but are presented here anyway. - See for details. - ''; - }; - s01Parse = mkOption { - type = types.listOf format.type; - default = [ ]; - description = '' - A list of stage s01-parse specifications. - See for details. - ''; - example = [ - { - filter = "1=1"; - debug = true; - onsuccess = "next_stage"; - name = "example/custom-service-logs"; - description = "Parsing custom service logs"; - grok = { - pattern = "^%{DATA:some_data}$"; - apply_on = "message"; - }; - statics = [ - { - parsed = "is_my_custom_service"; - value = "yes"; - } - ]; - } - ]; - }; - s02Enrich = mkOption { - type = types.listOf format.type; - default = [ ]; - description = '' - A list of stage s02-enrich specifications. Inside this list, you can specify Parser Whitelists. - See for details. - ''; - example = [ - { - name = "myips/whitelist"; - description = "Whitelist parse events from my IPs"; - whitelist = { - reason = "My IP ranges"; - ip = [ - "1.2.3.4" - ]; - cidr = [ - "1.2.3.0/24" - ]; - }; - } - ]; - }; - }; - }; - default = { }; - }; - postOverflows = mkOption { - type = types.submodule { - options = { - s01Whitelist = mkOption { - type = types.listOf format.type; - default = [ ]; - description = '' - A list of stage s01-whitelist specifications. Inside this list, you can specify Postoverflows Whitelists. - See for details. - ''; - example = [ - { - name = "postoverflows/whitelist_my_dns_domain"; - description = "Whitelist my reverse DNS"; - whitelist = { - reason = "Don't ban me"; - expression = [ - "evt.Enriched.reverse_dns endsWith '.local.'" - ]; - }; - } - ]; - }; - }; - }; - default = { }; - }; - contexts = mkOption { - type = types.listOf format.type; - description = '' - A list of additional contexts to specify. - See for details. - ''; - example = [ - { - context = { - target_uri = [ "evt.Meta.http_path" ]; - user_agent = [ "evt.Meta.http_user_agent" ]; - method = [ "evt.Meta.http_verb" ]; - status = [ "evt.Meta.http_status" ]; - }; - } - ]; - default = [ ]; - }; - notifications = mkOption { - type = types.listOf format.type; - description = '' - 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. - See for details. - ''; - example = [ - { - type = "http"; - name = "default_http_notification"; - log_level = "info"; - format = '' - {{.|toJson}} - ''; - url = "https://example.com/hook"; - method = "POST"; - } - ]; - default = [ ]; - }; - profiles = mkOption { - type = types.listOf format.type; - description = '' - A list of profiles to enable. - See for more details. - ''; - example = [ - { - name = "default_ip_remediation"; - filters = [ - "Alert.Remediation == true && Alert.GetScope() == 'Ip'" - ]; - decisions = [ - { - type = "ban"; - duration = "4h"; - } - ]; - on_success = "break"; - } - { - name = "default_range_remediation"; - filters = [ - "Alert.Remediation == true && Alert.GetScope() == 'Range'" - ]; - decisions = [ - { - type = "ban"; - duration = "4h"; - } - ]; - on_success = "break"; - } - ]; - default = [ - { - name = "default_ip_remediation"; - filters = [ - "Alert.Remediation == true && Alert.GetScope() == 'Ip'" - ]; - decisions = [ - { - type = "ban"; - duration = "4h"; - } - ]; - on_success = "break"; - } - { - name = "default_range_remediation"; - filters = [ - "Alert.Remediation == true && Alert.GetScope() == 'Range'" - ]; - decisions = [ - { - type = "ban"; - duration = "4h"; - } - ]; - on_success = "break"; - } - ]; - }; - patterns = mkOption { - type = types.listOf types.package; - default = [ ]; - example = lib.literalExpression '' - [ (pkgs.writeTextDir "custom_service_logs" (builtins.readFile ./custom_service_logs)) ] - ''; - }; - }; - }; - default = { }; - }; - - hub = mkOption { - type = types.submodule { - options = { - collections = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of hub collections to install"; - example = [ "crowdsecurity/linux" ]; - }; - - scenarios = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of hub scenarios to install"; - example = [ "crowdsecurity/ssh-bf" ]; - }; - - parsers = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of hub parsers to install"; - example = [ "crowdsecurity/sshd-logs" ]; - }; - - postOverflows = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of hub postoverflows to install"; - example = [ "crowdsecurity/auditd-nix-wrappers-whitelist-process" ]; - }; - - appSecConfigs = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of hub appsec configurations to install"; - example = [ "crowdsecurity/appsec-default" ]; - }; - - appSecRules = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of hub appsec rules to install"; - example = [ "crowdsecurity/base-config" ]; - }; - }; - }; - default = { }; - description = '' - Hub collections, parsers, AppSec rules, etc. - ''; - }; - - settings = mkOption { - type = types.submodule { - options = { - general = mkOption { - description = '' - Settings for the main CrowdSec configuration file. - Refer to the defaults at . - ''; - type = format.type; - default = { }; - }; - simulation = mkOption { - type = format.type; - default = { - simulation = false; - }; - description = '' - Attributes inside the simulation.yaml file. - ''; - }; - - lapi = mkOption { - type = types.submodule { - options = { - credentialsFile = mkOption { - type = types.nullOr types.path; - example = "/run/crowdsec/lapi.yaml"; - description = '' - The LAPI credential file to use. - ''; - default = null; - }; - }; - }; - description = '' - LAPI Configuration attributes - ''; - default = { }; - }; - capi = mkOption { - type = types.submodule { - options = { - credentialsFile = mkOption { - type = types.nullOr types.path; - example = "/run/crowdsec/capi.yaml"; - description = '' - The CAPI credential file to use. - ''; - default = null; - }; - }; - }; - description = '' - CAPI Configuration attributes - ''; - default = { }; - }; - console = mkOption { - type = types.submodule { - options = { - tokenFile = mkOption { - type = types.nullOr types.path; - example = "/run/crowdsec/console_token.yaml"; - description = '' - The Console Token file to use. - ''; - default = null; - }; - configuration = mkOption { - type = format.type; - default = { - share_manual_decisions = false; - share_custom = false; - share_tainted = false; - share_context = false; - }; - description = '' - Attributes inside the console.yaml file. - ''; - }; - }; - }; - description = '' - Console Configuration attributes - ''; - default = { }; - }; - }; - }; - }; - }; - config = - let - cfg = config.services.crowdsec; - configFile = format.generate "crowdsec.yaml" cfg.settings.general; - simulationFile = format.generate "simulation.yaml" cfg.settings.simulation; - consoleFile = format.generate "console.yaml" cfg.settings.console.configuration; - patternsDir = pkgs.buildPackages.symlinkJoin { - name = "crowdsec-patterns"; - paths = [ - cfg.localConfig.patterns - "${lib.attrsets.getOutput "out" cfg.package}/share/crowdsec/config/patterns/" - ]; - }; - - cscli = pkgs.writeShellScriptBin "cscli" '' - set -euo pipefail - # cscli needs crowdsec on it's path in order to be able to run `cscli explain` - export PATH="$PATH:${lib.makeBinPath [ cfg.package ]}" - sudo=exec - if [ "$USER" != "${cfg.user}" ]; then - sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}' - fi - $sudo ${lib.getExe' cfg.package "cscli"} -c=${configFile} "$@" - ''; - - localScenariosMap = (map (format.generate "scenario.yaml") cfg.localConfig.scenarios); - localParsersS00RawMap = ( - map (format.generate "parsers-s00-raw.yaml") cfg.localConfig.parsers.s00Raw - ); - localParsersS01ParseMap = ( - map (format.generate "parsers-s01-parse.yaml") cfg.localConfig.parsers.s01Parse - ); - localParsersS02EnrichMap = ( - map (format.generate "parsers-s02-enrich.yaml") cfg.localConfig.parsers.s02Enrich - ); - localPostOverflowsS01WhitelistMap = ( - map (format.generate "postoverflows-s01-whitelist.yaml") cfg.localConfig.postOverflows.s01Whitelist - ); - localContextsMap = (map (format.generate "context.yaml") cfg.localConfig.contexts); - localNotificationsMap = (map (format.generate "notification.yaml") cfg.localConfig.notifications); - localProfilesFile = pkgs.writeText "local_profiles.yaml" '' - --- - ${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.profiles} - --- - ''; - localAcquisisionFile = pkgs.writeText "local_acquisisions.yaml" '' - --- - ${lib.strings.concatMapStringsSep "\n---\n" builtins.toJSON cfg.localConfig.acquisitions} - --- - ''; - - scriptArray = - [ - "set -euo pipefail" - "${lib.getExe cscli} hub update" - ] - ++ lib.optionals (cfg.hub.collections != [ ]) [ - "${lib.getExe cscli} collections install ${ - lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.collections - }" - ] - ++ lib.optionals (cfg.hub.scenarios != [ ]) [ - "${lib.getExe cscli} scenarios install ${ - lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.scenarios - }" - ] - ++ lib.optionals (cfg.hub.parsers != [ ]) [ - "${lib.getExe cscli} parsers install ${ - lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.parsers - }" - ] - ++ lib.optionals (cfg.hub.postOverflows != [ ]) [ - "${lib.getExe cscli} postoverflows install ${ - lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.postOverflows - }" - ] - ++ lib.optionals (cfg.hub.appSecConfigs != [ ]) [ - "${lib.getExe cscli} appsec-configs install ${ - lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecConfigs - }" - ] - ++ lib.optionals (cfg.hub.appSecRules != [ ]) [ - "${lib.getExe cscli} appsec-rules install ${ - lib.strings.concatMapStringsSep " " (x: lib.escapeShellArg x) cfg.hub.appSecRules - }" - ] - ++ lib.optionals (cfg.settings.general.api.server.enable) [ - '' - if [ ! -s "${cfg.settings.general.api.client.credentials_path}" ]; then - ${lib.getExe cscli} machine add "${cfg.name}" --auto - fi - '' - ] - ++ lib.optionals (cfg.settings.capi.credentialsFile != null) [ - '' - if ! grep -q password "${cfg.settings.capi.credentialsFile}" ]; then - ${lib.getExe cscli} capi register - fi - '' - ] - ++ lib.optionals (cfg.settings.console.tokenFile != null) [ - '' - if [ ! -e "${cfg.settings.console.tokenFile}" ]; then - ${lib.getExe cscli} console enroll "$(cat ${cfg.settings.console.tokenFile})" --name ${cfg.name} - fi - '' - ]; - - setupScript = pkgs.writeShellScriptBin "crowdsec-setup" ( - lib.strings.concatStringsSep "\n" scriptArray - ); - - in - lib.mkIf (cfg.enable) { - - warnings = - [ ] - ++ lib.optionals (cfg.localConfig.profiles == [ ]) [ - "By not specifying profiles in services.crowdsec.localConfig.profiles, CrowdSec will not react to any alert by default." - ] - ++ lib.optionals (cfg.localConfig.acquisitions == [ ]) [ - "By not specifying acquisitions in services.crowdsec.localConfig.acquisitions, CrowdSec will not look for any data source." - ]; - - services.crowdsec.settings.general = with lib; { - common = { - daemonize = false; - log_media = "stdout"; - }; - config_paths = { - config_dir = confDir; - data_dir = stateDir; - simulation_path = simulationFile; - hub_dir = hubDir; - index_path = lib.strings.normalizePath "${stateDir}/hub/.index.json"; - notification_dir = notificationsDir; - plugin_dir = pluginDir; - pattern_dir = patternsDir; - }; - db_config = { - type = mkDefault "sqlite"; - db_path = mkDefault (lib.strings.normalizePath "${stateDir}/crowdsec.db"); - use_wal = mkDefault true; - }; - crowdsec_service = { - enable = mkDefault true; - acquisition_path = mkDefault localAcquisisionFile; - }; - api = { - client = { - credentials_path = cfg.settings.lapi.credentialsFile; - }; - server = { - enable = mkDefault false; - listen_uri = mkDefault "127.0.0.1:8080"; - - console_path = mkDefault consoleFile; - profiles_path = mkDefault localProfilesFile; - - online_client = mkDefault { - sharing = mkDefault true; - pull = mkDefault { - community = mkDefault true; - blocklists = mkDefault true; - }; - credentials_path = cfg.settings.capi.credentialsFile; - }; - }; - }; - prometheus = { - enabled = mkDefault true; - level = mkDefault "full"; - listen_addr = mkDefault "127.0.0.1"; - listen_port = mkDefault 6060; - }; - cscli = { - hub_branch = "v${cfg.package.version}"; - }; - }; - - environment = { - systemPackages = [ cscli ]; - }; - - systemd.packages = [ cfg.package ]; - - systemd.timers.crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) { - description = "Update the crowdsec hub index"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "daily"; - Persistent = "yes"; - Unit = "crowdsec-update-hub.service"; - }; - }; - systemd.services = { - crowdsec-update-hub = lib.mkIf (cfg.autoUpdateService) { - description = "Update the crowdsec hub index"; - serviceConfig = { - Type = "oneshot"; - User = cfg.user; - Group = cfg.group; - LimitNOFILE = 65536; - NoNewPrivileges = true; - LockPersonality = true; - RemoveIPC = true; - ReadWritePaths = [ - rootDir - confDir - ]; - ProtectSystem = "strict"; - PrivateUsers = true; - ProtectHome = true; - PrivateTmp = true; - PrivateDevices = true; - ProtectHostname = true; - UMask = "0077"; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - ProtectProc = "invisible"; - SystemCallFilter = [ - " " # This is needed to clear the SystemCallFilter existing definitions - "~@reboot" - "~@swap" - "~@obsolete" - "~@mount" - "~@module" - "~@debug" - "~@cpu-emulation" - "~@clock" - "~@raw-io" - "~@privileged" - "~@resources" - ]; - CapabilityBoundingSet = [ - " " # Reset all capabilities to an empty set - ]; - RestrictAddressFamilies = [ - " " # This is needed to clear the RestrictAddressFamilies existing definitions - "none" # Remove all addresses families - "AF_UNIX" - "AF_INET" - "AF_INET6" - ]; - DevicePolicy = "closed"; - ProtectKernelLogs = true; - SystemCallArchitectures = "native"; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - ExecStart = "${lib.getExe cscli} --error hub update"; - ExecStartPost = "systemctl reload crowdsec.service"; - }; - }; - - crowdsec = { - description = "CrowdSec is a free, modern & collaborative behavior detection engine, coupled with a global IP reputation network."; - wantedBy = [ "multi-user.target" ]; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - path = lib.mkForce [ ]; - environment = { - LC_ALL = "C"; - LANG = "C"; - }; - serviceConfig = { - User = cfg.user; - Group = cfg.group; - Type = "simple"; - RestartSec = 60; - LimitNOFILE = 65536; - NoNewPrivileges = true; - LockPersonality = true; - RemoveIPC = true; - ReadWritePaths = [ - rootDir - confDir - ]; - ProtectSystem = "strict"; - PrivateUsers = true; - ProtectHome = true; - PrivateTmp = true; - PrivateDevices = true; - ProtectHostname = true; - ProtectClock = true; - UMask = "0077"; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - ProtectProc = "invisible"; - SystemCallFilter = [ - " " # This is needed to clear the SystemCallFilter existing definitions - "~@reboot" - "~@swap" - "~@obsolete" - "~@mount" - "~@module" - "~@debug" - "~@cpu-emulation" - "~@clock" - "~@raw-io" - "~@privileged" - "~@resources" - ]; - CapabilityBoundingSet = [ - " " # Reset all capabilities to an empty set - "CAP_SYSLOG" # Add capability to read syslog - ]; - RestrictAddressFamilies = [ - " " # This is needed to clear the RestrictAddressFamilies existing definitions - "none" # Remove all addresses families - "AF_UNIX" - "AF_INET" - "AF_INET6" - ]; - DevicePolicy = "closed"; - ProtectKernelLogs = true; - SystemCallArchitectures = "native"; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - ExecReload = [ - " " # This is needed to clear the ExecReload definitions from upstream - ]; - ExecStart = [ - " " # This is needed to clear the ExecStart definitions from upstream - "${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -info" - ]; - ExecStartPre = [ - " " # This is needed to clear the ExecStartPre definitions from upstream - "${lib.getExe setupScript}" - "${lib.getExe' cfg.package "crowdsec"} -c ${configFile} -t -error" - ]; - }; - }; - }; - - systemd.tmpfiles.settings = { - "10-crowdsec" = - builtins.listToAttrs ( - map - (dirName: { - inherit cfg; - name = lib.strings.normalizePath dirName; - value = { - d = { - user = cfg.user; - group = cfg.group; - mode = "0750"; - }; - }; - }) - [ - stateDir - hubDir - confDir - localScenariosDir - localPostOverflowsDir - localPostOverflowsS01WhitelistDir - parsersDir - localParsersS00RawDir - localParsersS01ParseDir - localParsersS02EnrichDir - localContextsDir - notificationsDir - pluginDir - ] - ) - // builtins.listToAttrs ( - map (scenarioFile: { - inherit cfg; - name = lib.strings.normalizePath "${localScenariosDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf scenarioFile)}"; - value = { - link = { - type = "L+"; - argument = "${scenarioFile}"; - }; - }; - }) localScenariosMap - ) - // builtins.listToAttrs ( - map (parser: { - inherit cfg; - name = lib.strings.normalizePath "${localParsersS00RawDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; - value = { - link = { - type = "L+"; - argument = "${parser}"; - }; - }; - }) localParsersS00RawMap - ) - // builtins.listToAttrs ( - map (parser: { - inherit cfg; - name = lib.strings.normalizePath "${localParsersS01ParseDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; - value = { - link = { - type = "L+"; - argument = "${parser}"; - }; - }; - }) localParsersS01ParseMap - ) - // builtins.listToAttrs ( - map (parser: { - inherit cfg; - name = lib.strings.normalizePath "${localParsersS02EnrichDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf parser)}"; - value = { - link = { - type = "L+"; - argument = "${parser}"; - }; - }; - }) localParsersS02EnrichMap - ) - // builtins.listToAttrs ( - map (postoverflow: { - inherit cfg; - name = lib.strings.normalizePath "${localPostOverflowsS01WhitelistDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf postoverflow)}"; - value = { - link = { - type = "L+"; - argument = "${postoverflow}"; - }; - }; - }) localPostOverflowsS01WhitelistMap - ) - // builtins.listToAttrs ( - map (context: { - inherit cfg; - name = lib.strings.normalizePath "${localContextsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf context)}"; - value = { - link = { - type = "L+"; - argument = "${context}"; - }; - }; - }) localContextsMap - ) - // builtins.listToAttrs ( - map (notification: { - inherit cfg; - name = lib.strings.normalizePath "${notificationsDir}/${builtins.unsafeDiscardStringContext (builtins.baseNameOf notification)}"; - value = { - link = { - type = "L+"; - argument = "${notification}"; - }; - }; - }) localNotificationsMap - ); - }; - - users.users.${cfg.user} = { - name = cfg.user; - description = lib.mkDefault "CrowdSec service user"; - isSystemUser = true; - group = cfg.group; - extraGroups = [ "systemd-journal" ]; - }; - - users.groups.${cfg.group} = lib.mapAttrs (name: lib.mkDefault) { }; - - networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ - 6060 - 8080 - ]; - }; - - meta = { - maintainers = with lib.maintainers; [ - m0ustach3 - jk - ]; - }; -} - diff --git a/services/nginx/default.nix b/services/nginx/default.nix index f390c99..174ff26 100644 --- a/services/nginx/default.nix +++ b/services/nginx/default.nix @@ -29,6 +29,7 @@ in { age.secrets.nginx-capi-key = { file = ./secrets/cs-capi-key.age; owner = "crowdsec"; + mode = "600"; }; services = { crowdsec-firewall-bouncer = { diff --git a/services/nixarr/default.nix b/services/nixarr/default.nix index 2a0f6b7..8ef1d24 100644 --- a/services/nixarr/default.nix +++ b/services/nixarr/default.nix @@ -1,6 +1,5 @@ { config, - pkgs, lib, ... }: let @@ -39,6 +38,34 @@ in { type = "syslog"; }; } + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=sonarr.service" ]; + labels = { + type = "syslog"; + }; + } + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=bazarr.service" ]; + labels = { + type = "syslog"; + }; + } + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=prowlarr.service" ]; + labels = { + type = "syslog"; + }; + } + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=jellyseerr.service" ]; + labels = { + type = "syslog"; + }; + } ]; }; }; diff --git a/systems/minimalLXCConfig.nix b/systems/minimalLXCConfig.nix index 5540ada..e88dd7d 100644 --- a/systems/minimalLXCConfig.nix +++ b/systems/minimalLXCConfig.nix @@ -1,9 +1,5 @@ { - config, pkgs, - lib, - inputs, - modulesPath, ... }: { nix = { @@ -11,6 +7,21 @@ settings.trusted-users = ["root" "@wheel"]; }; + nixpkgs.overlays = [ + (final: prev: { + crowdsec = prev.crowdsec.overrideAttrs ( prev: rec { + pname = "crowdsec"; + version = "1.6.4"; + src = pkgs.fetchFromGitHub { + owner = "crowdsecurity"; + repo = "${pname}"; + tag = "v${version}"; + hash = "sha256-/NTlj0kYCOMxShfoKdmouJTiookDjccUj5HFHLPn5HI="; + }; + }); + }) + ]; + networking = { firewall = { enable = true; @@ -35,7 +46,7 @@ time.timeZone = "Europe/Paris"; console.keyMap = "fr"; i18n.defaultLocale = "fr_FR.UTF-8"; - environment.sessionVariables = rec { + environment.sessionVariables = { TERM = "xterm-256color"; }; @@ -97,6 +108,7 @@ nmap iperf3 netcat-openbsd + gnugrep ]; }; diff --git a/systems/minimalVMConfig.nix b/systems/minimalVMConfig.nix index 771c02d..96c12e2 100644 --- a/systems/minimalVMConfig.nix +++ b/systems/minimalVMConfig.nix @@ -1,8 +1,5 @@ { - config, pkgs, - lib, - inputs, - modulesPath, + pkgs, ... }: { nix = { @@ -10,6 +7,21 @@ settings.trusted-users = ["root" "@wheel"]; }; + nixpkgs.overlays = [ + (final: prev: { + crowdsec = prev.crowdsec.overrideAttrs ( prev: rec { + pname = "crowdsec"; + version = "1.6.4"; + src = pkgs.fetchFromGitHub { + owner = "crowdsecurity"; + repo = "${pname}"; + tag = "v${version}"; + hash = "sha256-/NTlj0kYCOMxShfoKdmouJTiookDjccUj5HFHLPn5HI="; + }; + }); + }) + ]; + networking = { firewall = { enable = true; @@ -21,7 +33,7 @@ time.timeZone = "Europe/Paris"; console.keyMap = "fr"; i18n.defaultLocale = "fr_FR.UTF-8"; - environment.sessionVariables = rec { + environment.sessionVariables = { TERM = "xterm-256color"; }; @@ -83,6 +95,7 @@ nmap iperf3 netcat-openbsd + gnugrep ]; };