diff options
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2311.section.md | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/networking/fastnetmon-advanced.nix | 222 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/fastnetmon-advanced.nix | 65 | ||||
-rw-r--r-- | pkgs/servers/fastnetmon-advanced/default.nix | 14 |
6 files changed, 302 insertions, 3 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 7375dd9d2a62..fb941d37edbe 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -107,6 +107,8 @@ - [NNCP](http://www.nncpgo.org/). Added nncp-daemon and nncp-caller services. Configuration is set with [programs.nncp.settings](#opt-programs.nncp.settings) and the daemons are enabled at [services.nncp](#opt-services.nncp.caller.enable). +- [FastNetMon Advanced](https://fastnetmon.com/product-overview/), a commercial high performance DDoS detector / sensor. Available as [services.fastnetmon-advanced](#opt-services.fastnetmon-advanced.enable). + - [tuxedo-rs](https://github.com/AaronErhardt/tuxedo-rs), Rust utilities for interacting with hardware from TUXEDO Computers. - [audiobookshelf](https://github.com/advplyr/audiobookshelf/), a self-hosted audiobook and podcast server. Available as [services.audiobookshelf](#opt-services.audiobookshelf.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 81b7b981a446..e54774a6a4ea 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -907,6 +907,7 @@ ./services/networking/eternal-terminal.nix ./services/networking/expressvpn.nix ./services/networking/fakeroute.nix + ./services/networking/fastnetmon-advanced.nix ./services/networking/ferm.nix ./services/networking/firefox-syncserver.nix ./services/networking/fireqos.nix diff --git a/nixos/modules/services/networking/fastnetmon-advanced.nix b/nixos/modules/services/networking/fastnetmon-advanced.nix new file mode 100644 index 000000000000..26e8ad8b76d9 --- /dev/null +++ b/nixos/modules/services/networking/fastnetmon-advanced.nix @@ -0,0 +1,222 @@ +{ config, lib, pkgs, ... }: + +let + # Background information: FastNetMon requires a MongoDB to start. This is because + # it uses MongoDB to store its configuration. That is, in a normal setup there is + # one collection with one document. + # To provide declarative configuration in our NixOS module, this database is + # completely emptied and replaced on each boot by the fastnetmon-setup service + # using the configuration backup functionality. + + cfg = config.services.fastnetmon-advanced; + settingsFormat = pkgs.formats.yaml { }; + + # obtain the default configs by starting up ferretdb and fcli in a derivation + default_configs = pkgs.runCommand "default-configs" { + nativeBuildInputs = [ + pkgs.ferretdb + pkgs.fastnetmon-advanced # for fcli + pkgs.proot + ]; + } '' + mkdir ferretdb fastnetmon $out + FERRETDB_TELEMETRY="disable" FERRETDB_HANDLER="sqlite" FERRETDB_STATE_DIR="$PWD/ferretdb" FERRETDB_SQLITE_URL="file:$PWD/ferretdb/" ferretdb & + + cat << EOF > fastnetmon/fastnetmon.conf + ${builtins.toJSON { + mongodb_username = ""; + }} + EOF + proot -b fastnetmon:/etc/fastnetmon -0 fcli create_configuration + proot -b fastnetmon:/etc/fastnetmon -0 fcli set bgp default + proot -b fastnetmon:/etc/fastnetmon -0 fcli export_configuration backup.tar + tar -C $out --no-same-owner -xvf backup.tar + ''; + + # merge the user configs into the default configs + config_tar = pkgs.runCommand "fastnetmon-config.tar" { + nativeBuildInputs = with pkgs; [ jq ]; + } '' + jq -s add ${default_configs}/main.json ${pkgs.writeText "main-add.json" (builtins.toJSON cfg.settings)} > main.json + mkdir hostgroup + ${lib.concatImapStringsSep "\n" (pos: hostgroup: '' + jq -s add ${default_configs}/hostgroup/0.json ${pkgs.writeText "hostgroup-${toString (pos - 1)}-add.json" (builtins.toJSON hostgroup)} > hostgroup/${toString (pos - 1)}.json + '') hostgroups} + mkdir bgp + ${lib.concatImapStringsSep "\n" (pos: bgp: '' + jq -s add ${default_configs}/bgp/0.json ${pkgs.writeText "bgp-${toString (pos - 1)}-add.json" (builtins.toJSON bgp)} > bgp/${toString (pos - 1)}.json + '') bgpPeers} + tar -cf $out main.json ${lib.concatImapStringsSep " " (pos: _: "hostgroup/${toString (pos - 1)}.json") hostgroups} ${lib.concatImapStringsSep " " (pos: _: "bgp/${toString (pos - 1)}.json") bgpPeers} + ''; + + hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups; + bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers; + +in { + options.services.fastnetmon-advanced = with lib; { + enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon"; + + settings = mkOption { + description = '' + Extra configuration options to declaratively load into FastNetMon Advanced. + + See the [FastNetMon Advanced Configuration options reference](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-configuration-options/) for more details. + ''; + type = settingsFormat.type; + default = {}; + example = literalExpression '' + { + networks_list = [ "192.0.2.0/24" ]; + gobgp = true; + gobgp_flow_spec_announces = true; + } + ''; + }; + hostgroups = mkOption { + description = "Hostgroups to declaratively load into FastNetMon Advanced"; + type = types.attrsOf settingsFormat.type; + default = {}; + }; + bgpPeers = mkOption { + description = "BGP Peers to declaratively load into FastNetMon Advanced"; + type = types.attrsOf settingsFormat.type; + default = {}; + }; + + enableAdvancedTrafficPersistence = mkOption { + description = "Store historical flow data in clickhouse"; + type = types.bool; + default = false; + }; + + traffic_db.settings = mkOption { + type = settingsFormat.type; + description = "Additional settings for /etc/fastnetmon/traffic_db.conf"; + }; + }; + + config = lib.mkMerge [ (lib.mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + fastnetmon-advanced # for fcli + ]; + + environment.etc."fastnetmon/license.lic".source = "/var/lib/fastnetmon/license.lic"; + environment.etc."fastnetmon/gobgpd.conf".source = "/run/fastnetmon/gobgpd.conf"; + environment.etc."fastnetmon/fastnetmon.conf".source = pkgs.writeText "fastnetmon.conf" (builtins.toJSON { + mongodb_username = ""; + }); + + services.ferretdb.enable = true; + + systemd.services.fastnetmon-setup = { + wantedBy = [ "multi-user.target" ]; + after = [ "ferretdb.service" ]; + path = with pkgs; [ fastnetmon-advanced config.systemd.package ]; + script = '' + fcli create_configuration + fcli delete hostgroup global + fcli import_configuration ${config_tar} + systemctl --no-block try-restart fastnetmon + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.services.fastnetmon = { + wantedBy = [ "multi-user.target" ]; + after = [ "ferretdb.service" "fastnetmon-setup.service" "polkit.service" ]; + path = with pkgs; [ iproute2 ]; + unitConfig = { + # Disable logic which shuts service when we do too many restarts + # We do restarts from sudo fcli commit and it's expected that we may have many restarts + # Details: https://github.com/systemd/systemd/issues/2416 + StartLimitInterval = 0; + }; + serviceConfig = { + ExecStart = "${pkgs.fastnetmon-advanced}/bin/fastnetmon --log_to_console"; + + LimitNOFILE = 65535; + # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened + Restart= "on-failure"; + RestartSec= "5s"; + + DynamicUser = true; + CacheDirectory = "fastnetmon"; + RuntimeDirectory = "fastnetmon"; # for gobgpd config + StateDirectory = "fastnetmon"; # for license file + }; + }; + + security.polkit.enable = true; + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.systemd1.manage-units" && + subject.isInGroup("fastnetmon")) { + if (action.lookup("unit") == "gobgp.service") { + var verb = action.lookup("verb"); + if (verb == "start" || verb == "stop" || verb == "restart") { + return polkit.Result.YES; + } + } + } + }); + ''; + + # We don't use the existing gobgp NixOS module and package, because the gobgp + # version might not be compatible with fastnetmon. Also, the service name + # _must_ be 'gobgp' and not 'gobgpd', so that fastnetmon can reload the config. + systemd.services.gobgp = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + description = "GoBGP Routing Daemon"; + unitConfig = { + ConditionPathExists = "/run/fastnetmon/gobgpd.conf"; + }; + serviceConfig = { + Type = "notify"; + ExecStartPre = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf -d"; + SupplementaryGroups = [ "fastnetmon" ]; + ExecStart = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf --sdnotify"; + ExecReload = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -r"; + DynamicUser = true; + AmbientCapabilities = "cap_net_bind_service"; + }; + }; + }) + + (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) { + ## Advanced Traffic persistence + ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/ + + services.clickhouse.enable = true; + + services.fastnetmon-advanced.settings.traffic_db = true; + + services.fastnetmon-advanced.traffic_db.settings = { + clickhouse_batch_size = lib.mkDefault 1000; + clickhouse_batch_delay = lib.mkDefault 1; + traffic_db_host = lib.mkDefault "127.0.0.1"; + traffic_db_port = lib.mkDefault 8100; + clickhouse_host = lib.mkDefault "127.0.0.1"; + clickhouse_port = lib.mkDefault 9000; + clickhouse_user = lib.mkDefault "default"; + clickhouse_password = lib.mkDefault ""; + }; + environment.etc."fastnetmon/traffic_db.conf".text = builtins.toJSON cfg.traffic_db.settings; + + systemd.services.traffic_db = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = "${pkgs.fastnetmon-advanced}/bin/traffic_db"; + # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened + Restart= "on-failure"; + RestartSec= "5s"; + + DynamicUser = true; + }; + }; + + }) ]; + + meta.maintainers = lib.teams.wdz.members; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 89e850d57376..5ea7bc9394c6 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -248,6 +248,7 @@ in { ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {}; ecryptfs = handleTest ./ecryptfs.nix {}; fscrypt = handleTest ./fscrypt.nix {}; + fastnetmon-advanced = runTest ./fastnetmon-advanced.nix; ejabberd = handleTest ./xmpp/ejabberd.nix {}; elk = handleTestOn ["x86_64-linux"] ./elk.nix {}; emacs-daemon = handleTest ./emacs-daemon.nix {}; diff --git a/nixos/tests/fastnetmon-advanced.nix b/nixos/tests/fastnetmon-advanced.nix new file mode 100644 index 000000000000..b2d2713a9211 --- /dev/null +++ b/nixos/tests/fastnetmon-advanced.nix @@ -0,0 +1,65 @@ +{ pkgs, lib, ... }: + +{ + name = "fastnetmon-advanced"; + meta.maintainers = lib.teams.wdz.members; + + nodes = { + bird = { ... }: { + networking.firewall.allowedTCPPorts = [ 179 ]; + services.bird2 = { + enable = true; + config = '' + router id 192.168.1.1; + + protocol bgp fnm { + local 192.168.1.1 as 64513; + neighbor 192.168.1.2 as 64514; + multihop; + ipv4 { + import all; + export none; + }; + } + ''; + }; + }; + fnm = { ... }: { + networking.firewall.allowedTCPPorts = [ 179 ]; + services.fastnetmon-advanced = { + enable = true; + settings = { + networks_list = [ "172.23.42.0/24" ]; + gobgp = true; + gobgp_flow_spec_announces = true; + }; + bgpPeers = { + bird = { + local_asn = 64514; + remote_asn = 64513; + local_address = "192.168.1.2"; + remote_address = "192.168.1.1"; + + description = "Bird"; + ipv4_unicast = true; + multihop = true; + active = true; + }; + }; + }; + }; + }; + + testScript = { nodes, ... }: '' + start_all() + fnm.wait_for_unit("fastnetmon.service") + bird.wait_for_unit("bird2.service") + + fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "BGP daemon restarted correctly"') + fnm.wait_until_succeeds("journalctl -eu gobgp.service | grep BGP_FSM_OPENCONFIRM") + bird.wait_until_succeeds("birdc show protocol fnm | grep Estab") + fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "API server listening"') + fnm.succeed("fcli set blackhole 172.23.42.123") + bird.succeed("birdc show route | grep 172.23.42.123") + ''; +} diff --git a/pkgs/servers/fastnetmon-advanced/default.nix b/pkgs/servers/fastnetmon-advanced/default.nix index 76994cac91bb..f08e4662c93e 100644 --- a/pkgs/servers/fastnetmon-advanced/default.nix +++ b/pkgs/servers/fastnetmon-advanced/default.nix @@ -1,12 +1,18 @@ -{ lib, stdenv, fetchurl, autoPatchelfHook, bzip2 }: +{ lib +, stdenv +, fetchurl +, autoPatchelfHook +, bzip2 +, nixosTests +}: stdenv.mkDerivation rec { pname = "fastnetmon-advanced"; - version = "2.0.350"; + version = "2.0.351"; src = fetchurl { url = "https://repo.fastnetmon.com/fastnetmon_ubuntu_jammy/pool/fastnetmon/f/fastnetmon/fastnetmon_${version}_amd64.deb"; - hash = "sha256-rd0xdpENsdH8jOoUkQHW8/fXE4zEjQemFT4Q2tXjtT8="; + hash = "sha256-gLR4Z5VZyyt6CmoWcqDT75o50KyEJsfsx67Sqpiwh04="; }; nativeBuildInputs = [ @@ -58,6 +64,8 @@ stdenv.mkDerivation rec { $out/bin/fnm-gobgpd --help 2>&1 | grep "Application Options" ''; + passthru.tests = { inherit (nixosTests) fastnetmon-advanced; }; + meta = with lib; { description = "A high performance DDoS detector / sensor - commercial edition"; homepage = "https://fastnetmon.com"; |