at 24.11-pre 8.7 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 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 55in { 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}