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