diff --git a/configuration/services/crowdsec.nix b/configuration/services/crowdsec.nix index a9c790c..4566943 100644 --- a/configuration/services/crowdsec.nix +++ b/configuration/services/crowdsec.nix @@ -1,7 +1,8 @@ { pkgs, ... }: { - services.crowdsec = { + security.crowdsec = { enable = true; + remediationComponents.firewallBouncer.enable = true; parserWhitelist = [ "1.64.239.213" @@ -17,7 +18,7 @@ --- source: journalctl journalctl_filter: - - "SYSLOG_IDENTIFIER=sshd-service" + - "SYSLOG_IDENTIFIER=sshd-session" labels: type: syslog --- diff --git a/flake.nix b/flake.nix index 444d4b8..3d04d7c 100644 --- a/flake.nix +++ b/flake.nix @@ -100,10 +100,16 @@ # Garbage collection root # ########################### - packages.${system} = { - default = vm.config.system.build.vm; - crowdsec = pkgs.callPackage "${inputs.nixpkgs-crowdsec}/pkgs/by-name/cr/crowdsec/package.nix" { }; - }; + packages.${system} = + let + localPkgs = import ./pkgs { inherit pkgs; }; + in + { + default = vm.config.system.build.vm; + crowdsec = pkgs.callPackage "${inputs.nixpkgs-crowdsec}/pkgs/by-name/cr/crowdsec/package.nix" { }; + crowdsec-hub = localPkgs.crowdsec.hub; + crowdsec-firewall-bouncer = localPkgs.crowdsec.firewall-bouncer; + }; ################### # Utility scripts # @@ -120,6 +126,22 @@ ${vm.config.system.build.vm.outPath}/bin/run-testvm-vm '').outPath; }; + + update-crowdsec-packages = + let + git = pkgs.lib.getExe pkgs.git; + nvfetcher = pkgs.lib.getExe pkgs.nvfetcher; + in + { + type = "app"; + program = + (pkgs.writeShellScript "update-crowdsec-packages" '' + cd "$(${git} rev-parse --show-toplevel)" + cd ./pkgs/crowdsec + ${nvfetcher} + echo 'Remember to update the vendorHash of any go packages!' + '').outPath; + }; }; ########################### diff --git a/modules/crowdsec.nix b/modules/crowdsec/default.nix similarity index 88% rename from modules/crowdsec.nix rename to modules/crowdsec/default.nix index 82dfabd..169c47f 100644 --- a/modules/crowdsec.nix +++ b/modules/crowdsec/default.nix @@ -6,7 +6,7 @@ ... }: let - cfg = config.services.crowdsec; + cfg = config.security.crowdsec; settingsFormat = pkgs.formats.yaml { }; crowdsec = flake-inputs.self.packages.${pkgs.system}.crowdsec; @@ -31,7 +31,9 @@ let ''; in { - options.services.crowdsec = + imports = [ ./remediations ]; + + options.security.crowdsec = let inherit (lib.types) nullOr @@ -81,6 +83,50 @@ in ''; }; + hubConfigurations = { + collections = lib.mkOption { + type = listOf str; + description = '' + List of pre-made crowdsec collections to install. + ''; + }; + + scenarios = lib.mkOption { + type = listOf str; + description = '' + List of pre-made crowdsec scenarios to install. + ''; + }; + + parsers = lib.mkOption { + type = listOf str; + description = '' + List of pre-made crowdsec parsers to install. + ''; + }; + + postoverflows = lib.mkOption { + type = listOf str; + description = '' + List of pre-made crowdsec postoverflows to install. + ''; + }; + + appsecConfigs = lib.mkOption { + type = listOf str; + description = '' + List of pre-made crowdsec appsec configurations to install. + ''; + }; + + appsecRules = lib.mkOption { + type = listOf str; + description = '' + List of pre-made crowdsec appsec rules to install. + ''; + }; + }; + centralApiCredentials = lib.mkOption { type = nullOr path; default = null; @@ -115,7 +161,7 @@ in # Set up default settings; anything that *shouldn't* be changed is # set to the default priority so that users need to use # `lib.mkForce`. - services.crowdsec = { + security.crowdsec = { stateDirectory = "/var/lib/crowdsec"; settings = { @@ -278,6 +324,8 @@ in # Note that the service basics are already defined upstream crowdsec = { + enable = true; + after = [ "crowdsec-setup.service" ]; bindsTo = [ "crowdsec-setup.service" ]; diff --git a/modules/crowdsec/remediations/cs-firewall-bouncer.nix b/modules/crowdsec/remediations/cs-firewall-bouncer.nix new file mode 100644 index 0000000..b08578e --- /dev/null +++ b/modules/crowdsec/remediations/cs-firewall-bouncer.nix @@ -0,0 +1,90 @@ +{ + flake-inputs, + pkgs, + lib, + config, + ... +}: +let + crowdsecCfg = config.security.crowdsec; + cfg = crowdsecCfg.remediationComponents.firewallBouncer; + settingsFormat = pkgs.formats.yaml { }; + crowdsec-firewall-bouncer = flake-inputs.self.packages.${pkgs.system}.crowdsec-firewall-bouncer; +in +{ + options.security.crowdsec.remediationComponents.firewallBouncer = { + enable = lib.mkEnableOption "cs-firewall-bouncer"; + + settings = lib.mkOption { + inherit (settingsFormat) type; + default = { }; + + description = '' + The bouncer configuration. Refer to + <https://docs.crowdsec.net/u/bouncers/firewall/> for details + on supported values. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + security.crowdsec.remediationComponents.firewallBouncer.settings = { + mode = lib.mkDefault "${if config.networking.nftables.enable then "nftables" else "iptables"}"; + log_mode = "stdout"; + iptables_chains = [ + "nixos-fw" + ]; + + # Don't let users easily override this; unfortunately we need to + # set up this key through substitution at runtime. + api_key = lib.mkForce "\${API_KEY}"; + api_url = lib.mkDefault "http://${crowdsecCfg.settings.api.server.listen_uri}"; + }; + + systemd = { + packages = [ crowdsec-firewall-bouncer ]; + + services = { + crowdsec-firewall-bouncer-setup = { + description = "Crowdsec firewall bouncer config preparation"; + script = '' + if [ ! -e '${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml' ]; then + ${crowdsecCfg.package}/bin/cscli -oraw bouncers add "cs-firewall-bouncer-$(${pkgs.coreutils}/bin/date +%s)" > \ + ${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml + fi + + # Stdout redirection is deliberately used to forcibly + # overwrite the file if it exists + API_KEY="$(<${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml)" \ + ${lib.getExe pkgs.envsubst} \ + -i ${settingsFormat.generate "crowdsec-firewall-bouncer.yaml" cfg.settings} \ + > /var/lib/crowdsec/config/crowdsec-firewall-bouncer.yaml + ''; + + serviceConfig = { + User = "crowdsec"; + Group = "crowdsec"; + + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + + crowdsec-firewall-bouncer = { + enable = true; + + after = [ "crowdsec-firewall-bouncer-setup.service" ]; + bindsTo = [ "crowdsec-firewall-bouncer-setup.service" ]; + partOf = [ "crowdsec.service" ]; + + path = + lib.optionals (cfg.settings.mode == "ipset" || cfg.settings.mode == "iptables") [ + pkgs.ipset + ] + ++ lib.optional (cfg.settings.mode == "iptables") pkgs.iptables + ++ lib.optional (cfg.settings.mode == "nftables") pkgs.nftables; + }; + }; + }; + }; +} diff --git a/modules/crowdsec/remediations/default.nix b/modules/crowdsec/remediations/default.nix new file mode 100644 index 0000000..7df6ade --- /dev/null +++ b/modules/crowdsec/remediations/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./cs-firewall-bouncer.nix + ]; +} diff --git a/modules/default.nix b/modules/default.nix index 977539a..89f1752 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,6 +1,6 @@ { imports = [ - ./crowdsec.nix + ./crowdsec ./nginxExtensions.nix ]; } diff --git a/pkgs/crowdsec/_sources/generated.json b/pkgs/crowdsec/_sources/generated.json new file mode 100644 index 0000000..8485779 --- /dev/null +++ b/pkgs/crowdsec/_sources/generated.json @@ -0,0 +1,42 @@ +{ + "crowdsec-firewall-bouncer": { + "cargoLocks": null, + "date": null, + "extract": null, + "name": "crowdsec-firewall-bouncer", + "passthru": null, + "pinned": false, + "src": { + "deepClone": false, + "fetchSubmodules": false, + "leaveDotGit": false, + "name": null, + "owner": "crowdsecurity", + "repo": "cs-firewall-bouncer", + "rev": "v0.0.31", + "sha256": "sha256-59MWll8v00CF4WA53gjHZSTFc8hpYaHENg9O7LgTCrA=", + "type": "github" + }, + "version": "v0.0.31" + }, + "crowdsec-hub": { + "cargoLocks": null, + "date": "2025-01-30", + "extract": null, + "name": "crowdsec-hub", + "passthru": null, + "pinned": false, + "src": { + "deepClone": false, + "fetchSubmodules": false, + "leaveDotGit": false, + "name": null, + "owner": "crowdsecurity", + "repo": "hub", + "rev": "8f102f5ac79af59d3024ca2771b65ec87411ac02", + "sha256": "sha256-8K1HkBg0++Au1dr2KMrl9b2ruqXdo+vqWngOCwL11Mo=", + "type": "github" + }, + "version": "8f102f5ac79af59d3024ca2771b65ec87411ac02" + } +} \ No newline at end of file diff --git a/pkgs/crowdsec/_sources/generated.nix b/pkgs/crowdsec/_sources/generated.nix new file mode 100644 index 0000000..6f845ec --- /dev/null +++ b/pkgs/crowdsec/_sources/generated.nix @@ -0,0 +1,27 @@ +# This file was generated by nvfetcher, please do not modify it manually. +{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }: +{ + crowdsec-firewall-bouncer = { + pname = "crowdsec-firewall-bouncer"; + version = "v0.0.31"; + src = fetchFromGitHub { + owner = "crowdsecurity"; + repo = "cs-firewall-bouncer"; + rev = "v0.0.31"; + fetchSubmodules = false; + sha256 = "sha256-59MWll8v00CF4WA53gjHZSTFc8hpYaHENg9O7LgTCrA="; + }; + }; + crowdsec-hub = { + pname = "crowdsec-hub"; + version = "8f102f5ac79af59d3024ca2771b65ec87411ac02"; + src = fetchFromGitHub { + owner = "crowdsecurity"; + repo = "hub"; + rev = "8f102f5ac79af59d3024ca2771b65ec87411ac02"; + fetchSubmodules = false; + sha256 = "sha256-8K1HkBg0++Au1dr2KMrl9b2ruqXdo+vqWngOCwL11Mo="; + }; + date = "2025-01-30"; + }; +} diff --git a/pkgs/crowdsec/default.nix b/pkgs/crowdsec/default.nix new file mode 100644 index 0000000..66faac3 --- /dev/null +++ b/pkgs/crowdsec/default.nix @@ -0,0 +1,9 @@ +{ pkgs }: +let + sources = pkgs.callPackage ./_sources/generated.nix { }; + callPackage = pkgs.lib.callPackageWith (pkgs // { inherit sources; }); +in +{ + hub = callPackage ./hub.nix { }; + firewall-bouncer = callPackage ./firewall-bouncer.nix { }; +} diff --git a/pkgs/crowdsec/firewall-bouncer.nix b/pkgs/crowdsec/firewall-bouncer.nix new file mode 100644 index 0000000..86370c4 --- /dev/null +++ b/pkgs/crowdsec/firewall-bouncer.nix @@ -0,0 +1,26 @@ +{ + lib, + sources, + buildGoModule, + envsubst, + coreutils, +}: +let + envsubstBin = lib.getExe envsubst; +in +buildGoModule { + inherit (sources.crowdsec-firewall-bouncer) pname version src; + + vendorHash = "sha256-7Jxvg8UEjUxnIz1llvXyI2AefJ31OVdNzhWD/C8wU/Y="; + + postInstall = '' + mkdir -p $out/lib/systemd/system + + CFG=/var/lib/crowdsec/config BIN=$out/bin/cs-firewall-bouncer ${envsubstBin} \ + -i ./config/crowdsec-firewall-bouncer.service \ + -o $out/lib/systemd/system/crowdsec-firewall-bouncer.service + + substituteInPlace $out/lib/systemd/system/crowdsec-firewall-bouncer.service \ + --replace-fail /bin/sleep ${coreutils}/bin/sleep + ''; +} diff --git a/pkgs/crowdsec/hub.nix b/pkgs/crowdsec/hub.nix new file mode 100644 index 0000000..d057ca8 --- /dev/null +++ b/pkgs/crowdsec/hub.nix @@ -0,0 +1,4 @@ +{ + sources, +}: +sources.crowdsec-hub.src diff --git a/pkgs/crowdsec/nvfetcher.toml b/pkgs/crowdsec/nvfetcher.toml new file mode 100644 index 0000000..2287dba --- /dev/null +++ b/pkgs/crowdsec/nvfetcher.toml @@ -0,0 +1,7 @@ +[crowdsec-hub] +src.git = "https://github.com/crowdsecurity/hub.git" +fetch.github = "crowdsecurity/hub" + +[crowdsec-firewall-bouncer] +src.github = "crowdsecurity/cs-firewall-bouncer" +fetch.github = "crowdsecurity/cs-firewall-bouncer" diff --git a/pkgs/default.nix b/pkgs/default.nix index 036afd4..0e5de7a 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,7 +1,5 @@ { pkgs }: -let - inherit (pkgs) callPackage; -in { - starbound = callPackage ./starbound { }; + crowdsec = import ./crowdsec { inherit pkgs; }; + starbound = pkgs.callPackage ./starbound { }; }