Merge pull request #231065 from yu-re-ka/fnm-advanced-module

nixos/fastnetmon-advanced: init

authored by Janik and committed by GitHub e46b352a 6d3cf22d

+302 -3
+2
nixos/doc/manual/release-notes/rl-2311.section.md
··· 107 107 108 108 - [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). 109 109 110 + - [FastNetMon Advanced](https://fastnetmon.com/product-overview/), a commercial high performance DDoS detector / sensor. Available as [services.fastnetmon-advanced](#opt-services.fastnetmon-advanced.enable). 111 + 110 112 - [tuxedo-rs](https://github.com/AaronErhardt/tuxedo-rs), Rust utilities for interacting with hardware from TUXEDO Computers. 111 113 112 114 - [audiobookshelf](https://github.com/advplyr/audiobookshelf/), a self-hosted audiobook and podcast server. Available as [services.audiobookshelf](#opt-services.audiobookshelf.enable).
+1
nixos/modules/module-list.nix
··· 907 907 ./services/networking/eternal-terminal.nix 908 908 ./services/networking/expressvpn.nix 909 909 ./services/networking/fakeroute.nix 910 + ./services/networking/fastnetmon-advanced.nix 910 911 ./services/networking/ferm.nix 911 912 ./services/networking/firefox-syncserver.nix 912 913 ./services/networking/fireqos.nix
+222
nixos/modules/services/networking/fastnetmon-advanced.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + let 4 + # Background information: FastNetMon requires a MongoDB to start. This is because 5 + # it uses MongoDB to store its configuration. That is, in a normal setup there is 6 + # one collection with one document. 7 + # To provide declarative configuration in our NixOS module, this database is 8 + # completely emptied and replaced on each boot by the fastnetmon-setup service 9 + # using the configuration backup functionality. 10 + 11 + cfg = config.services.fastnetmon-advanced; 12 + settingsFormat = pkgs.formats.yaml { }; 13 + 14 + # obtain the default configs by starting up ferretdb and fcli in a derivation 15 + default_configs = pkgs.runCommand "default-configs" { 16 + nativeBuildInputs = [ 17 + pkgs.ferretdb 18 + pkgs.fastnetmon-advanced # for fcli 19 + pkgs.proot 20 + ]; 21 + } '' 22 + mkdir ferretdb fastnetmon $out 23 + FERRETDB_TELEMETRY="disable" FERRETDB_HANDLER="sqlite" FERRETDB_STATE_DIR="$PWD/ferretdb" FERRETDB_SQLITE_URL="file:$PWD/ferretdb/" ferretdb & 24 + 25 + cat << EOF > fastnetmon/fastnetmon.conf 26 + ${builtins.toJSON { 27 + mongodb_username = ""; 28 + }} 29 + EOF 30 + proot -b fastnetmon:/etc/fastnetmon -0 fcli create_configuration 31 + proot -b fastnetmon:/etc/fastnetmon -0 fcli set bgp default 32 + proot -b fastnetmon:/etc/fastnetmon -0 fcli export_configuration backup.tar 33 + tar -C $out --no-same-owner -xvf backup.tar 34 + ''; 35 + 36 + # merge the user configs into the default configs 37 + config_tar = pkgs.runCommand "fastnetmon-config.tar" { 38 + nativeBuildInputs = with pkgs; [ jq ]; 39 + } '' 40 + jq -s add ${default_configs}/main.json ${pkgs.writeText "main-add.json" (builtins.toJSON cfg.settings)} > main.json 41 + mkdir hostgroup 42 + ${lib.concatImapStringsSep "\n" (pos: hostgroup: '' 43 + jq -s add ${default_configs}/hostgroup/0.json ${pkgs.writeText "hostgroup-${toString (pos - 1)}-add.json" (builtins.toJSON hostgroup)} > hostgroup/${toString (pos - 1)}.json 44 + '') hostgroups} 45 + mkdir bgp 46 + ${lib.concatImapStringsSep "\n" (pos: bgp: '' 47 + jq -s add ${default_configs}/bgp/0.json ${pkgs.writeText "bgp-${toString (pos - 1)}-add.json" (builtins.toJSON bgp)} > bgp/${toString (pos - 1)}.json 48 + '') bgpPeers} 49 + tar -cf $out main.json ${lib.concatImapStringsSep " " (pos: _: "hostgroup/${toString (pos - 1)}.json") hostgroups} ${lib.concatImapStringsSep " " (pos: _: "bgp/${toString (pos - 1)}.json") bgpPeers} 50 + ''; 51 + 52 + hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups; 53 + bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers; 54 + 55 + in { 56 + options.services.fastnetmon-advanced = with lib; { 57 + enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon"; 58 + 59 + settings = mkOption { 60 + description = '' 61 + Extra configuration options to declaratively load into FastNetMon Advanced. 62 + 63 + See the [FastNetMon Advanced Configuration options reference](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-configuration-options/) for more details. 64 + ''; 65 + type = settingsFormat.type; 66 + default = {}; 67 + example = literalExpression '' 68 + { 69 + networks_list = [ "192.0.2.0/24" ]; 70 + gobgp = true; 71 + gobgp_flow_spec_announces = true; 72 + } 73 + ''; 74 + }; 75 + hostgroups = mkOption { 76 + description = "Hostgroups to declaratively load into FastNetMon Advanced"; 77 + type = types.attrsOf settingsFormat.type; 78 + default = {}; 79 + }; 80 + bgpPeers = mkOption { 81 + description = "BGP Peers to declaratively load into FastNetMon Advanced"; 82 + type = types.attrsOf settingsFormat.type; 83 + default = {}; 84 + }; 85 + 86 + enableAdvancedTrafficPersistence = mkOption { 87 + description = "Store historical flow data in clickhouse"; 88 + type = types.bool; 89 + default = false; 90 + }; 91 + 92 + traffic_db.settings = mkOption { 93 + type = settingsFormat.type; 94 + description = "Additional settings for /etc/fastnetmon/traffic_db.conf"; 95 + }; 96 + }; 97 + 98 + config = lib.mkMerge [ (lib.mkIf cfg.enable { 99 + environment.systemPackages = with pkgs; [ 100 + fastnetmon-advanced # for fcli 101 + ]; 102 + 103 + environment.etc."fastnetmon/license.lic".source = "/var/lib/fastnetmon/license.lic"; 104 + environment.etc."fastnetmon/gobgpd.conf".source = "/run/fastnetmon/gobgpd.conf"; 105 + environment.etc."fastnetmon/fastnetmon.conf".source = pkgs.writeText "fastnetmon.conf" (builtins.toJSON { 106 + mongodb_username = ""; 107 + }); 108 + 109 + services.ferretdb.enable = true; 110 + 111 + systemd.services.fastnetmon-setup = { 112 + wantedBy = [ "multi-user.target" ]; 113 + after = [ "ferretdb.service" ]; 114 + path = with pkgs; [ fastnetmon-advanced config.systemd.package ]; 115 + script = '' 116 + fcli create_configuration 117 + fcli delete hostgroup global 118 + fcli import_configuration ${config_tar} 119 + systemctl --no-block try-restart fastnetmon 120 + ''; 121 + serviceConfig.Type = "oneshot"; 122 + }; 123 + 124 + systemd.services.fastnetmon = { 125 + wantedBy = [ "multi-user.target" ]; 126 + after = [ "ferretdb.service" "fastnetmon-setup.service" "polkit.service" ]; 127 + path = with pkgs; [ iproute2 ]; 128 + unitConfig = { 129 + # Disable logic which shuts service when we do too many restarts 130 + # We do restarts from sudo fcli commit and it's expected that we may have many restarts 131 + # Details: https://github.com/systemd/systemd/issues/2416 132 + StartLimitInterval = 0; 133 + }; 134 + serviceConfig = { 135 + ExecStart = "${pkgs.fastnetmon-advanced}/bin/fastnetmon --log_to_console"; 136 + 137 + LimitNOFILE = 65535; 138 + # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened 139 + Restart= "on-failure"; 140 + RestartSec= "5s"; 141 + 142 + DynamicUser = true; 143 + CacheDirectory = "fastnetmon"; 144 + RuntimeDirectory = "fastnetmon"; # for gobgpd config 145 + StateDirectory = "fastnetmon"; # for license file 146 + }; 147 + }; 148 + 149 + security.polkit.enable = true; 150 + security.polkit.extraConfig = '' 151 + polkit.addRule(function(action, subject) { 152 + if (action.id == "org.freedesktop.systemd1.manage-units" && 153 + subject.isInGroup("fastnetmon")) { 154 + if (action.lookup("unit") == "gobgp.service") { 155 + var verb = action.lookup("verb"); 156 + if (verb == "start" || verb == "stop" || verb == "restart") { 157 + return polkit.Result.YES; 158 + } 159 + } 160 + } 161 + }); 162 + ''; 163 + 164 + # We don't use the existing gobgp NixOS module and package, because the gobgp 165 + # version might not be compatible with fastnetmon. Also, the service name 166 + # _must_ be 'gobgp' and not 'gobgpd', so that fastnetmon can reload the config. 167 + systemd.services.gobgp = { 168 + wantedBy = [ "multi-user.target" ]; 169 + after = [ "network.target" ]; 170 + description = "GoBGP Routing Daemon"; 171 + unitConfig = { 172 + ConditionPathExists = "/run/fastnetmon/gobgpd.conf"; 173 + }; 174 + serviceConfig = { 175 + Type = "notify"; 176 + ExecStartPre = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf -d"; 177 + SupplementaryGroups = [ "fastnetmon" ]; 178 + ExecStart = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf --sdnotify"; 179 + ExecReload = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -r"; 180 + DynamicUser = true; 181 + AmbientCapabilities = "cap_net_bind_service"; 182 + }; 183 + }; 184 + }) 185 + 186 + (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) { 187 + ## Advanced Traffic persistence 188 + ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/ 189 + 190 + services.clickhouse.enable = true; 191 + 192 + services.fastnetmon-advanced.settings.traffic_db = true; 193 + 194 + services.fastnetmon-advanced.traffic_db.settings = { 195 + clickhouse_batch_size = lib.mkDefault 1000; 196 + clickhouse_batch_delay = lib.mkDefault 1; 197 + traffic_db_host = lib.mkDefault "127.0.0.1"; 198 + traffic_db_port = lib.mkDefault 8100; 199 + clickhouse_host = lib.mkDefault "127.0.0.1"; 200 + clickhouse_port = lib.mkDefault 9000; 201 + clickhouse_user = lib.mkDefault "default"; 202 + clickhouse_password = lib.mkDefault ""; 203 + }; 204 + environment.etc."fastnetmon/traffic_db.conf".text = builtins.toJSON cfg.traffic_db.settings; 205 + 206 + systemd.services.traffic_db = { 207 + wantedBy = [ "multi-user.target" ]; 208 + after = [ "network.target" ]; 209 + serviceConfig = { 210 + ExecStart = "${pkgs.fastnetmon-advanced}/bin/traffic_db"; 211 + # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened 212 + Restart= "on-failure"; 213 + RestartSec= "5s"; 214 + 215 + DynamicUser = true; 216 + }; 217 + }; 218 + 219 + }) ]; 220 + 221 + meta.maintainers = lib.teams.wdz.members; 222 + }
+1
nixos/tests/all-tests.nix
··· 248 248 ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {}; 249 249 ecryptfs = handleTest ./ecryptfs.nix {}; 250 250 fscrypt = handleTest ./fscrypt.nix {}; 251 + fastnetmon-advanced = runTest ./fastnetmon-advanced.nix; 251 252 ejabberd = handleTest ./xmpp/ejabberd.nix {}; 252 253 elk = handleTestOn ["x86_64-linux"] ./elk.nix {}; 253 254 emacs-daemon = handleTest ./emacs-daemon.nix {};
+65
nixos/tests/fastnetmon-advanced.nix
··· 1 + { pkgs, lib, ... }: 2 + 3 + { 4 + name = "fastnetmon-advanced"; 5 + meta.maintainers = lib.teams.wdz.members; 6 + 7 + nodes = { 8 + bird = { ... }: { 9 + networking.firewall.allowedTCPPorts = [ 179 ]; 10 + services.bird2 = { 11 + enable = true; 12 + config = '' 13 + router id 192.168.1.1; 14 + 15 + protocol bgp fnm { 16 + local 192.168.1.1 as 64513; 17 + neighbor 192.168.1.2 as 64514; 18 + multihop; 19 + ipv4 { 20 + import all; 21 + export none; 22 + }; 23 + } 24 + ''; 25 + }; 26 + }; 27 + fnm = { ... }: { 28 + networking.firewall.allowedTCPPorts = [ 179 ]; 29 + services.fastnetmon-advanced = { 30 + enable = true; 31 + settings = { 32 + networks_list = [ "172.23.42.0/24" ]; 33 + gobgp = true; 34 + gobgp_flow_spec_announces = true; 35 + }; 36 + bgpPeers = { 37 + bird = { 38 + local_asn = 64514; 39 + remote_asn = 64513; 40 + local_address = "192.168.1.2"; 41 + remote_address = "192.168.1.1"; 42 + 43 + description = "Bird"; 44 + ipv4_unicast = true; 45 + multihop = true; 46 + active = true; 47 + }; 48 + }; 49 + }; 50 + }; 51 + }; 52 + 53 + testScript = { nodes, ... }: '' 54 + start_all() 55 + fnm.wait_for_unit("fastnetmon.service") 56 + bird.wait_for_unit("bird2.service") 57 + 58 + fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "BGP daemon restarted correctly"') 59 + fnm.wait_until_succeeds("journalctl -eu gobgp.service | grep BGP_FSM_OPENCONFIRM") 60 + bird.wait_until_succeeds("birdc show protocol fnm | grep Estab") 61 + fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "API server listening"') 62 + fnm.succeed("fcli set blackhole 172.23.42.123") 63 + bird.succeed("birdc show route | grep 172.23.42.123") 64 + ''; 65 + }
+11 -3
pkgs/servers/fastnetmon-advanced/default.nix
··· 1 - { lib, stdenv, fetchurl, autoPatchelfHook, bzip2 }: 1 + { lib 2 + , stdenv 3 + , fetchurl 4 + , autoPatchelfHook 5 + , bzip2 6 + , nixosTests 7 + }: 2 8 3 9 stdenv.mkDerivation rec { 4 10 pname = "fastnetmon-advanced"; 5 - version = "2.0.350"; 11 + version = "2.0.351"; 6 12 7 13 src = fetchurl { 8 14 url = "https://repo.fastnetmon.com/fastnetmon_ubuntu_jammy/pool/fastnetmon/f/fastnetmon/fastnetmon_${version}_amd64.deb"; 9 - hash = "sha256-rd0xdpENsdH8jOoUkQHW8/fXE4zEjQemFT4Q2tXjtT8="; 15 + hash = "sha256-gLR4Z5VZyyt6CmoWcqDT75o50KyEJsfsx67Sqpiwh04="; 10 16 }; 11 17 12 18 nativeBuildInputs = [ ··· 57 63 $out/bin/fnm-gobgp --help 2>&1 | grep "Available Commands" 58 64 $out/bin/fnm-gobgpd --help 2>&1 | grep "Application Options" 59 65 ''; 66 + 67 + passthru.tests = { inherit (nixosTests) fastnetmon-advanced; }; 60 68 61 69 meta = with lib; { 62 70 description = "A high performance DDoS detector / sensor - commercial edition";