Merge #361424: refactor lib.packagesFromDirectoryRecursive (v2)
[NixPkgs.git] / nixos / modules / services / networking / fastnetmon-advanced.nix
blobc57b5a6b660037103de8cb217da7f004eda3fe32
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
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.
16   cfg = config.services.fastnetmon-advanced;
17   settingsFormat = pkgs.formats.yaml { };
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 &
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       '';
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       '';
65   hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups;
66   bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers;
70   options.services.fastnetmon-advanced = with lib; {
71     enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon";
73     settings = mkOption {
74       description = ''
75         Extra configuration options to declaratively load into FastNetMon Advanced.
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     };
100     enableAdvancedTrafficPersistence = mkOption {
101       description = "Store historical flow data in clickhouse";
102       type = types.bool;
103       default = false;
104     };
106     traffic_db.settings = mkOption {
107       type = settingsFormat.type;
108       description = "Additional settings for /etc/fastnetmon/traffic_db.conf";
109     };
110   };
112   config = lib.mkMerge [
113     (lib.mkIf cfg.enable {
114       environment.systemPackages = with pkgs; [
115         fastnetmon-advanced # for fcli
116       ];
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       );
126       services.ferretdb.enable = true;
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       };
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";
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";
166           DynamicUser = true;
167           CacheDirectory = "fastnetmon";
168           RuntimeDirectory = "fastnetmon"; # for gobgpd config
169           StateDirectory = "fastnetmon"; # for license file
170         };
171       };
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";
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     })
212     (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) {
213       ## Advanced Traffic persistence
214       ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/
216       services.clickhouse.enable = true;
218       services.fastnetmon-advanced.settings.traffic_db = true;
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;
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";
241           DynamicUser = true;
242         };
243       };
245     })
246   ];
248   meta.maintainers = lib.teams.wdz.members;