normcap: fix on GNOME wayland when used via keybind or alt-f2 (#351763)
[NixPkgs.git] / nixos / modules / services / networking / gns3-server.nix
blob769cde21bc20160f6b0d955747917d7ff40c99c9
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
9   cfg = config.services.gns3-server;
11   settingsFormat = pkgs.formats.ini { };
12   configFile = settingsFormat.generate "gns3-server.conf" cfg.settings;
16   meta = {
17     doc = ./gns3-server.md;
18     maintainers = [ lib.maintainers.anthonyroussel ];
19   };
21   options = {
22     services.gns3-server = {
23       enable = lib.mkEnableOption "GNS3 Server daemon";
25       package = lib.mkPackageOption pkgs "gns3-server" { };
27       auth = {
28         enable = lib.mkEnableOption "password based HTTP authentication to access the GNS3 Server";
30         user = lib.mkOption {
31           type = lib.types.nullOr lib.types.str;
32           default = null;
33           example = "gns3";
34           description = ''Username used to access the GNS3 Server.'';
35         };
37         passwordFile = lib.mkOption {
38           type = lib.types.nullOr lib.types.path;
39           default = null;
40           example = "/run/secrets/gns3-server-password";
41           description = ''
42             A file containing the password to access the GNS3 Server.
44             ::: {.warning}
45             This should be a string, not a nix path, since nix paths
46             are copied into the world-readable nix store.
47             :::
48           '';
49         };
50       };
52       settings = lib.mkOption {
53         type = lib.types.submodule { freeformType = settingsFormat.type; };
54         default = { };
55         example = {
56           host = "127.0.0.1";
57           port = 3080;
58         };
59         description = ''
60           The global options in `config` file in ini format.
62           Refer to <https://docs.gns3.com/docs/using-gns3/administration/gns3-server-configuration-file/>
63           for all available options.
64         '';
65       };
67       log = {
68         file = lib.mkOption {
69           type = lib.types.nullOr lib.types.path;
70           default = "/var/log/gns3/server.log";
71           description = ''Path of the file GNS3 Server should log to.'';
72         };
74         debug = lib.mkEnableOption "debug logging";
75       };
77       ssl = {
78         enable = lib.mkEnableOption "SSL encryption";
80         certFile = lib.mkOption {
81           type = lib.types.nullOr lib.types.path;
82           default = null;
83           example = "/var/lib/gns3/ssl/server.pem";
84           description = ''
85             Path to the SSL certificate file. This certificate will
86             be offered to, and may be verified by, clients.
87           '';
88         };
90         keyFile = lib.mkOption {
91           type = lib.types.nullOr lib.types.path;
92           default = null;
93           example = "/var/lib/gns3/ssl/server.key";
94           description = "Private key file for the certificate.";
95         };
96       };
98       dynamips = {
99         enable = lib.mkEnableOption ''Dynamips support'';
100         package = lib.mkPackageOption pkgs "dynamips" { };
101       };
103       ubridge = {
104         enable = lib.mkEnableOption ''uBridge support'';
105         package = lib.mkPackageOption pkgs "ubridge" { };
106       };
108       vpcs = {
109         enable = lib.mkEnableOption ''VPCS support'';
110         package = lib.mkPackageOption pkgs "vpcs" { };
111       };
112     };
113   };
115   config =
116     let
117       flags = {
118         enableDocker = config.virtualisation.docker.enable;
119         enableLibvirtd = config.virtualisation.libvirtd.enable;
120       };
122     in
123     lib.mkIf cfg.enable {
124       assertions = [
125         {
126           assertion = cfg.ssl.enable -> cfg.ssl.certFile != null;
127           message = "Please provide a certificate to use for SSL encryption.";
128         }
129         {
130           assertion = cfg.ssl.enable -> cfg.ssl.keyFile != null;
131           message = "Please provide a private key to use for SSL encryption.";
132         }
133         {
134           assertion = cfg.auth.enable -> cfg.auth.user != null;
135           message = "Please provide a username to use for HTTP authentication.";
136         }
137         {
138           assertion = cfg.auth.enable -> cfg.auth.passwordFile != null;
139           message = "Please provide a password file to use for HTTP authentication.";
140         }
141       ];
143       users.groups.gns3 = { };
145       users.groups.ubridge = lib.mkIf cfg.ubridge.enable { };
147       users.users.gns3 = {
148         group = "gns3";
149         isSystemUser = true;
150       };
152       security.wrappers.ubridge = lib.mkIf cfg.ubridge.enable {
153         capabilities = "cap_net_raw,cap_net_admin=eip";
154         group = "ubridge";
155         owner = "root";
156         permissions = "u=rwx,g=rx,o=r";
157         source = lib.getExe cfg.ubridge.package;
158       };
160       services.gns3-server.settings = lib.mkMerge [
161         {
162           Server = {
163             appliances_path = lib.mkDefault "/var/lib/gns3/appliances";
164             configs_path = lib.mkDefault "/var/lib/gns3/configs";
165             images_path = lib.mkDefault "/var/lib/gns3/images";
166             projects_path = lib.mkDefault "/var/lib/gns3/projects";
167             symbols_path = lib.mkDefault "/var/lib/gns3/symbols";
168           };
169         }
170         (lib.mkIf (cfg.ubridge.enable) {
171           Server.ubridge_path = lib.mkDefault "/run/wrappers/bin/ubridge";
172         })
173         (lib.mkIf (cfg.auth.enable) {
174           Server = {
175             auth = lib.mkDefault (lib.boolToString cfg.auth.enable);
176             user = lib.mkDefault cfg.auth.user;
177             password = lib.mkDefault "@AUTH_PASSWORD@";
178           };
179         })
180         (lib.mkIf (cfg.vpcs.enable) {
181           VPCS.vpcs_path = lib.mkDefault (lib.getExe cfg.vpcs.package);
182         })
183         (lib.mkIf (cfg.dynamips.enable) {
184           Dynamips.dynamips_path = lib.mkDefault (lib.getExe cfg.dynamips.package);
185         })
186       ];
188       systemd.services.gns3-server =
189         let
190           commandArgs = lib.cli.toGNUCommandLineShell { } {
191             config = "/etc/gns3/gns3_server.conf";
192             pid = "/run/gns3/server.pid";
193             log = cfg.log.file;
194             ssl = cfg.ssl.enable;
195             # These are implicitly not set if `null`
196             certfile = cfg.ssl.certFile;
197             certkey = cfg.ssl.keyFile;
198           };
199         in
200         {
201           description = "GNS3 Server";
203           after = [
204             "network.target"
205             "network-online.target"
206           ];
207           wantedBy = [ "multi-user.target" ];
208           wants = [ "network-online.target" ];
210           # configFile cannot be stored in RuntimeDirectory, because GNS3
211           # uses the `--config` base path to stores supplementary configuration files at runtime.
212           #
213           preStart = ''
214             install -m660 ${configFile} /etc/gns3/gns3_server.conf
216             ${lib.optionalString cfg.auth.enable ''
217               ${pkgs.replace-secret}/bin/replace-secret \
218                 '@AUTH_PASSWORD@' \
219                 "''${CREDENTIALS_DIRECTORY}/AUTH_PASSWORD" \
220                 /etc/gns3/gns3_server.conf
221             ''}
222           '';
224           path = lib.optional flags.enableLibvirtd pkgs.qemu;
226           reloadTriggers = [ configFile ];
228           serviceConfig = {
229             ConfigurationDirectory = "gns3";
230             ConfigurationDirectoryMode = "0750";
231             Environment = "HOME=%S/gns3";
232             ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
233             ExecStart = "${lib.getExe cfg.package} ${commandArgs}";
234             Group = "gns3";
235             LimitNOFILE = 16384;
236             LoadCredential = lib.mkIf cfg.auth.enable [ "AUTH_PASSWORD:${cfg.auth.passwordFile}" ];
237             LogsDirectory = "gns3";
238             LogsDirectoryMode = "0750";
239             PIDFile = "/run/gns3/server.pid";
240             Restart = "on-failure";
241             RestartSec = 5;
242             RuntimeDirectory = "gns3";
243             StateDirectory = "gns3";
244             StateDirectoryMode = "0750";
245             SupplementaryGroups =
246               lib.optional flags.enableDocker "docker"
247               ++ lib.optional flags.enableLibvirtd "libvirtd"
248               ++ lib.optional cfg.ubridge.enable "ubridge";
249             User = "gns3";
250             WorkingDirectory = "%S/gns3";
252             # Required for ubridge integration to work
253             #
254             # GNS3 needs to run SUID binaries (ubridge)
255             # but NoNewPrivileges breaks execution of SUID binaries
256             DynamicUser = false;
257             NoNewPrivileges = false;
258             RestrictSUIDSGID = false;
259             PrivateUsers = false;
261             # Hardening
262             DeviceAllow =
263               [
264                 # ubridge needs access to tun/tap devices
265                 "/dev/net/tap rw"
266                 "/dev/net/tun rw"
267               ]
268               ++ lib.optionals flags.enableLibvirtd [
269                 "/dev/kvm"
270               ];
271             DevicePolicy = "closed";
272             LockPersonality = true;
273             MemoryDenyWriteExecute = true;
274             PrivateTmp = true;
275             # Don't restrict ProcSubset because python3Packages.psutil requires read access to /proc/stat
276             # ProcSubset = "pid";
277             ProtectClock = true;
278             ProtectControlGroups = true;
279             ProtectHome = true;
280             ProtectHostname = true;
281             ProtectKernelLogs = true;
282             ProtectKernelModules = true;
283             ProtectKernelTunables = true;
284             ProtectProc = "invisible";
285             ProtectSystem = "strict";
286             RestrictAddressFamilies = [
287               "AF_INET"
288               "AF_INET6"
289               "AF_NETLINK"
290               "AF_UNIX"
291               "AF_PACKET"
292             ];
293             RestrictNamespaces = true;
294             RestrictRealtime = true;
295             UMask = "0022";
296           };
297         };
298     };