at 25.11-pre 9.3 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 # Background information: FastNetMon requires a MongoDB to start. This is because 10 # it uses MongoDB to store its configuration. That is, in a normal setup there is 11 # one collection with one document. 12 # To provide declarative configuration in our NixOS module, this database is 13 # completely emptied and replaced on each boot by the fastnetmon-setup service 14 # using the configuration backup functionality. 15 16 cfg = config.services.fastnetmon-advanced; 17 settingsFormat = pkgs.formats.yaml { }; 18 19 # obtain the default configs by starting up ferretdb and fcli in a derivation 20 default_configs = 21 pkgs.runCommand "default-configs" 22 { 23 nativeBuildInputs = [ 24 pkgs.ferretdb 25 pkgs.fastnetmon-advanced # for fcli 26 pkgs.proot 27 ]; 28 } 29 '' 30 mkdir ferretdb fastnetmon $out 31 FERRETDB_TELEMETRY="disable" FERRETDB_HANDLER="sqlite" FERRETDB_STATE_DIR="$PWD/ferretdb" FERRETDB_SQLITE_URL="file:$PWD/ferretdb/" ferretdb & 32 33 cat << EOF > fastnetmon/fastnetmon.conf 34 ${builtins.toJSON { 35 mongodb_username = ""; 36 }} 37 EOF 38 proot -b fastnetmon:/etc/fastnetmon -0 fcli create_configuration 39 proot -b fastnetmon:/etc/fastnetmon -0 fcli set bgp default 40 proot -b fastnetmon:/etc/fastnetmon -0 fcli export_configuration backup.tar 41 tar -C $out --no-same-owner -xvf backup.tar 42 ''; 43 44 # merge the user configs into the default configs 45 config_tar = 46 pkgs.runCommand "fastnetmon-config.tar" 47 { 48 nativeBuildInputs = with pkgs; [ jq ]; 49 } 50 '' 51 jq -s add ${default_configs}/main.json ${pkgs.writeText "main-add.json" (builtins.toJSON cfg.settings)} > main.json 52 mkdir hostgroup 53 ${lib.concatImapStringsSep "\n" (pos: hostgroup: '' 54 jq -s add ${default_configs}/hostgroup/0.json ${pkgs.writeText "hostgroup-${toString (pos - 1)}-add.json" (builtins.toJSON hostgroup)} > hostgroup/${toString (pos - 1)}.json 55 '') hostgroups} 56 mkdir bgp 57 ${lib.concatImapStringsSep "\n" (pos: bgp: '' 58 jq -s add ${default_configs}/bgp/0.json ${pkgs.writeText "bgp-${toString (pos - 1)}-add.json" (builtins.toJSON bgp)} > bgp/${toString (pos - 1)}.json 59 '') bgpPeers} 60 tar -cf $out main.json ${ 61 lib.concatImapStringsSep " " (pos: _: "hostgroup/${toString (pos - 1)}.json") hostgroups 62 } ${lib.concatImapStringsSep " " (pos: _: "bgp/${toString (pos - 1)}.json") bgpPeers} 63 ''; 64 65 hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups; 66 bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers; 67 68in 69{ 70 options.services.fastnetmon-advanced = with lib; { 71 enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon"; 72 73 settings = mkOption { 74 description = '' 75 Extra configuration options to declaratively load into FastNetMon Advanced. 76 77 See the [FastNetMon Advanced Configuration options reference](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-configuration-options/) for more details. 78 ''; 79 type = settingsFormat.type; 80 default = { }; 81 example = literalExpression '' 82 { 83 networks_list = [ "192.0.2.0/24" ]; 84 gobgp = true; 85 gobgp_flow_spec_announces = true; 86 } 87 ''; 88 }; 89 hostgroups = mkOption { 90 description = "Hostgroups to declaratively load into FastNetMon Advanced"; 91 type = types.attrsOf settingsFormat.type; 92 default = { }; 93 }; 94 bgpPeers = mkOption { 95 description = "BGP Peers to declaratively load into FastNetMon Advanced"; 96 type = types.attrsOf settingsFormat.type; 97 default = { }; 98 }; 99 100 enableAdvancedTrafficPersistence = mkOption { 101 description = "Store historical flow data in clickhouse"; 102 type = types.bool; 103 default = false; 104 }; 105 106 traffic_db.settings = mkOption { 107 type = settingsFormat.type; 108 description = "Additional settings for /etc/fastnetmon/traffic_db.conf"; 109 }; 110 }; 111 112 config = lib.mkMerge [ 113 (lib.mkIf cfg.enable { 114 environment.systemPackages = with pkgs; [ 115 fastnetmon-advanced # for fcli 116 ]; 117 118 environment.etc."fastnetmon/license.lic".source = "/var/lib/fastnetmon/license.lic"; 119 environment.etc."fastnetmon/gobgpd.conf".source = "/run/fastnetmon/gobgpd.conf"; 120 environment.etc."fastnetmon/fastnetmon.conf".source = pkgs.writeText "fastnetmon.conf" ( 121 builtins.toJSON { 122 mongodb_username = ""; 123 } 124 ); 125 126 services.ferretdb.enable = true; 127 128 systemd.services.fastnetmon-setup = { 129 wantedBy = [ "multi-user.target" ]; 130 after = [ "ferretdb.service" ]; 131 path = with pkgs; [ 132 fastnetmon-advanced 133 config.systemd.package 134 ]; 135 script = '' 136 fcli create_configuration 137 fcli delete hostgroup global 138 fcli import_configuration ${config_tar} 139 systemctl --no-block try-restart fastnetmon 140 ''; 141 serviceConfig.Type = "oneshot"; 142 }; 143 144 systemd.services.fastnetmon = { 145 wantedBy = [ "multi-user.target" ]; 146 after = [ 147 "ferretdb.service" 148 "fastnetmon-setup.service" 149 "polkit.service" 150 ]; 151 path = with pkgs; [ iproute2 ]; 152 unitConfig = { 153 # Disable logic which shuts service when we do too many restarts 154 # We do restarts from sudo fcli commit and it's expected that we may have many restarts 155 # Details: https://github.com/systemd/systemd/issues/2416 156 StartLimitInterval = 0; 157 }; 158 serviceConfig = { 159 ExecStart = "${pkgs.fastnetmon-advanced}/bin/fastnetmon --log_to_console"; 160 161 LimitNOFILE = 65535; 162 # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened 163 Restart = "on-failure"; 164 RestartSec = "5s"; 165 166 DynamicUser = true; 167 CacheDirectory = "fastnetmon"; 168 RuntimeDirectory = "fastnetmon"; # for gobgpd config 169 StateDirectory = "fastnetmon"; # for license file 170 }; 171 }; 172 173 security.polkit.enable = true; 174 security.polkit.extraConfig = '' 175 polkit.addRule(function(action, subject) { 176 if (action.id == "org.freedesktop.systemd1.manage-units" && 177 subject.isInGroup("fastnetmon")) { 178 if (action.lookup("unit") == "gobgp.service") { 179 var verb = action.lookup("verb"); 180 if (verb == "start" || verb == "stop" || verb == "restart") { 181 return polkit.Result.YES; 182 } 183 } 184 } 185 }); 186 ''; 187 # dbus/polkit with DynamicUser is broken with the default implementation 188 services.dbus.implementation = "broker"; 189 190 # We don't use the existing gobgp NixOS module and package, because the gobgp 191 # version might not be compatible with fastnetmon. Also, the service name 192 # _must_ be 'gobgp' and not 'gobgpd', so that fastnetmon can reload the config. 193 systemd.services.gobgp = { 194 wantedBy = [ "multi-user.target" ]; 195 after = [ "network.target" ]; 196 description = "GoBGP Routing Daemon"; 197 unitConfig = { 198 ConditionPathExists = "/run/fastnetmon/gobgpd.conf"; 199 }; 200 serviceConfig = { 201 Type = "notify"; 202 ExecStartPre = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf -d"; 203 SupplementaryGroups = [ "fastnetmon" ]; 204 ExecStart = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf --sdnotify"; 205 ExecReload = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -r"; 206 DynamicUser = true; 207 AmbientCapabilities = "cap_net_bind_service"; 208 }; 209 }; 210 }) 211 212 (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) { 213 ## Advanced Traffic persistence 214 ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/ 215 216 services.clickhouse.enable = true; 217 218 services.fastnetmon-advanced.settings.traffic_db = true; 219 220 services.fastnetmon-advanced.traffic_db.settings = { 221 clickhouse_batch_size = lib.mkDefault 1000; 222 clickhouse_batch_delay = lib.mkDefault 1; 223 traffic_db_host = lib.mkDefault "127.0.0.1"; 224 traffic_db_port = lib.mkDefault 8100; 225 clickhouse_host = lib.mkDefault "127.0.0.1"; 226 clickhouse_port = lib.mkDefault 9000; 227 clickhouse_user = lib.mkDefault "default"; 228 clickhouse_password = lib.mkDefault ""; 229 }; 230 environment.etc."fastnetmon/traffic_db.conf".text = builtins.toJSON cfg.traffic_db.settings; 231 232 systemd.services.traffic_db = { 233 wantedBy = [ "multi-user.target" ]; 234 after = [ "network.target" ]; 235 serviceConfig = { 236 ExecStart = "${pkgs.fastnetmon-advanced}/bin/traffic_db"; 237 # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened 238 Restart = "on-failure"; 239 RestartSec = "5s"; 240 241 DynamicUser = true; 242 }; 243 }; 244 245 }) 246 ]; 247 248 meta.maintainers = lib.teams.wdz.members; 249}