diff --git a/nixos/modules/services/security/crowdsec-firewall-bouncer.nix b/nixos/modules/services/security/crowdsec-firewall-bouncer.nix index eb75840b67793..03a87578cab82 100644 --- a/nixos/modules/services/security/crowdsec-firewall-bouncer.nix +++ b/nixos/modules/services/security/crowdsec-firewall-bouncer.nix @@ -230,25 +230,41 @@ in wantedBy = [ "multi-user.target" ]; after = [ "crowdsec.service" ]; wants = after; + path = [ config.services.crowdsec.configuredCscli ]; script = '' - cscli=${lib.getExe config.services.crowdsec.configuredCscli} - if $cscli bouncers list --output json | ${lib.getExe pkgs.jq} -e -- ${lib.escapeShellArg "any(.[]; .name == \"${cfg.registerBouncer.bouncerName}\")"} >/dev/null; then - # Bouncer already registered. Verify the API key is still present + # Ensure the directory exists + mkdir -p "$(dirname ${apiKeyFile})" || true + + echo "Checking bouncer registration..." + if cscli bouncers list --output json | ${lib.getExe pkgs.jq} -e -- ${lib.escapeShellArg "any(.[]; .name == \"${cfg.registerBouncer.bouncerName}\")"} >/dev/null; then + + echo "Bouncer already registered. Verify the API key is still present" if [ ! -f ${apiKeyFile} ]; then echo "Bouncer registered but API key is not present" - exit 1 + echo "Unregistering bouncer..." + cscli bouncers delete ${cfg.registerBouncer.bouncerName} || true + else + echo "API key file exists, nothing to do" + exit 0 fi else - # Bouncer not registered - # Remove any previously saved API key + echo "Bouncer not registered" + echo "Remove any previously saved API key" rm -f '${apiKeyFile}' - # Register the bouncer and save the new API key - if ! $cscli bouncers add --output raw -- ${lib.escapeShellArg cfg.registerBouncer.bouncerName} >${apiKeyFile}; then - # Failed to register the bouncer - rm ${apiKeyFile} + fi + + echo "Register the bouncer and save the new API key" + if ! cscli bouncers add --output raw -- ${lib.escapeShellArg cfg.registerBouncer.bouncerName} > ${apiKeyFile} 2>&1; then + echo "Failed to register the bouncer" + cat ${apiKeyFile} || true # Show error message + rm -f ${apiKeyFile} exit 1 - fi fi + + chmod 0440 ${apiKeyFile} || true + echo "Successfully registered bouncer and saved API key" + + cscli bouncers list ''; serviceConfig = { Type = "oneshot"; @@ -384,6 +400,8 @@ in "~@resources" ]; UMask = "0077"; + + Restart = "always"; }; }; }; diff --git a/nixos/modules/services/security/crowdsec.nix b/nixos/modules/services/security/crowdsec.nix index 46e454f83afde..706843f66bf08 100644 --- a/nixos/modules/services/security/crowdsec.nix +++ b/nixos/modules/services/security/crowdsec.nix @@ -327,7 +327,12 @@ in online_client.credentials_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; - description = "Path to a file containing credentials for the Central API."; + example = "\${config_paths.data_dir}/online_api_credentials.yaml"; + description = '' + Path to a file containing credentials for the Central API. + To automatically register with `crowdsec-setup`, set this option (typically to ''${config_paths.data_dir}/online_api_credentials.yaml). + The file will be automatically created, unless it already exists. + ''; }; }; }; @@ -343,6 +348,18 @@ in }; prometheus = { + enabled = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable or disable the CrowdSec prometheus exporter."; + }; + + listen_addr = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Prometheus listen address."; + }; + listen_port = lib.mkOption { type = lib.types.port; default = 6060; @@ -627,14 +644,22 @@ in let installDir = d: ''install -d -o ${cfg.user} -g ${cfg.group} -m 750 "${d}"''; - setupConfigDirs = lib.concatMapStringsSep "\n" installDir [ + cleanConfigDirs = '' + if [ -d ${config_paths.config_dir}/patterns ]; then + rm -rf ${config_paths.config_dir}/patterns + fi + ''; + + createConfigDirs = lib.concatMapStringsSep "\n" installDir [ config_paths.config_dir cfg.settings.config.crowdsec_service.acquisition_dir config_paths.notification_dir "${config_paths.config_dir}/appsec-configs" "${config_paths.config_dir}/appsec-rules" "${config_paths.config_dir}/collections" + "${config_paths.config_dir}/console" "${config_paths.config_dir}/contexts" + "${config_paths.config_dir}/hub" "${config_paths.config_dir}/parsers" "${config_paths.config_dir}/parsers/s00-raw" "${config_paths.config_dir}/parsers/s01-parse" @@ -645,6 +670,11 @@ in "${config_paths.config_dir}/scenarios" ]; + setupConfigDirs = '' + ${cleanConfigDirs} + ${createConfigDirs} + ''; + setupScript = pkgs.writeShellApplication { name = "crowdsec-setup"; runtimeInputs = [ configuredCscli ]; @@ -661,21 +691,53 @@ in install -o ${cfg.user} -g ${cfg.group} -m 0750 -D ${cfg.package}/libexec/crowdsec/plugins/${name} ${cfg.settings.config.config_paths.data_dir}/plugins/${name} ''; - maybeTouchCredPath = + maybeTouchFile = p: lib.optionalString (p != null) '' if [ ! -s ${p} ]; then touch "${p}" fi ''; + + maybeInstallConfigFile = + p: o: + lib.optionalString (p != null) '' + if [ ! -f ${config_paths.config_dir}/${o} ]; then + cp ${cfg.package}/share/crowdsec/config/${p} ${config_paths.config_dir}/${o} + fi + ''; + + maybeInstallDataFile = + p: o: + lib.optionalString (p != null) '' + if [ ! -f ${cfg.settings.config.config_paths.data_dir}/${o} ]; then + cp ${cfg.package}/share/crowdsec/config/${p} ${cfg.settings.config.config_paths.data_dir}/${o} + fi + ''; + + overwriteInstallConfigDir = + p: + lib.optionalString (p != null) '' + cp -a ${cfg.package}/share/crowdsec/config/${p} ${config_paths.config_dir} + ''; in '' - ${maybeTouchCredPath cfg.settings.config.api.client.credentials_path} - ${maybeTouchCredPath cfg.settings.config.api.server.online_client.credentials_path} + ${maybeTouchFile cfg.settings.config.api.client.credentials_path} + ${maybeTouchFile cfg.settings.config.api.server.online_client.credentials_path} ${installDir cfg.settings.config.config_paths.hub_dir} ${installDir cfg.settings.config.config_paths.plugin_dir} + # needed by `cscli setup` + ${installDir "${cfg.settings.config.config_paths.hub_dir}/.cache"} + ${installDir "${cfg.settings.config.config_paths.data_dir}/data"} + ${maybeInstallDataFile "detect.yaml" "data/detect.yaml"} + + ${maybeInstallConfigFile "simulation.yaml" "simulation.yaml"} + ${maybeInstallConfigFile "context.yaml" "console/context.yaml"} + ${maybeInstallConfigFile "console.yaml" "console.yaml"} + ${overwriteInstallConfigDir "patterns"} + echo "Updating hub..." cscli hub update @@ -781,24 +843,35 @@ in environment = { systemPackages = let - cscliWrapper = pkgs.writeShellScriptBin "cscli" '' - exec systemd-run \ - --quiet \ - --pty \ - --wait \ - --collect \ - --pipe \ - --property=ExecPaths="${cfg.settings.config.config_paths.plugin_dir}" \ - --property=User=${cfg.user} \ - --property=Group=${cfg.group} \ - --property=DynamicUser=true \ - --property=StateDirectory="crowdsec crowdsec/hub" \ - --property=StateDirectoryMode="0750" \ - --property=ConfigurationDirectory="crowdsec crowdsec/acquis.d" \ - --property=ConfigurationDirectoryMode="0750" \ - -- \ - ${lib.getExe configuredCscli} "$@" - ''; + cscliWrapper = pkgs.symlinkJoin { + name = "cscli"; + paths = [ + (pkgs.writeShellScriptBin "cscli" '' + exec systemd-run \ + --quiet \ + --pty \ + --wait \ + --collect \ + --pipe \ + --property=ExecPaths="${cfg.settings.config.config_paths.plugin_dir}" \ + --property=User=${cfg.user} \ + --property=Group=${cfg.group} \ + --property=DynamicUser=true \ + --property=StateDirectory="crowdsec crowdsec/hub" \ + --property=StateDirectoryMode="0750" \ + --property=ConfigurationDirectory="crowdsec crowdsec/acquis.d" \ + --property=ConfigurationDirectoryMode="0750" \ + -- \ + ${lib.getExe configuredCscli} "$@" + '') + (pkgs.runCommand "cscli-completions" { } '' + mkdir -p $out/share + ln -s ${cfg.package}/share/bash-completion $out/share/bash-completion + ln -s ${cfg.package}/share/zsh $out/share/zsh + ln -s ${cfg.package}/share/fish $out/share/fish + '') + ]; + }; in [ cscliWrapper ];