at 25.11-pre 9.5 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.gns3-server; 10 11 settingsFormat = pkgs.formats.ini { }; 12 configFile = settingsFormat.generate "gns3-server.conf" cfg.settings; 13 14in 15{ 16 meta = { 17 doc = ./gns3-server.md; 18 maintainers = [ lib.maintainers.anthonyroussel ]; 19 }; 20 21 options = { 22 services.gns3-server = { 23 enable = lib.mkEnableOption "GNS3 Server daemon"; 24 25 package = lib.mkPackageOption pkgs "gns3-server" { }; 26 27 auth = { 28 enable = lib.mkEnableOption "password based HTTP authentication to access the GNS3 Server"; 29 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 }; 36 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. 43 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 }; 51 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. 61 62 Refer to <https://docs.gns3.com/docs/using-gns3/administration/gns3-server-configuration-file/> 63 for all available options. 64 ''; 65 }; 66 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 }; 73 74 debug = lib.mkEnableOption "debug logging"; 75 }; 76 77 ssl = { 78 enable = lib.mkEnableOption "SSL encryption"; 79 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 }; 89 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 }; 97 98 dynamips = { 99 enable = lib.mkEnableOption ''Dynamips support''; 100 package = lib.mkPackageOption pkgs "dynamips" { }; 101 }; 102 103 ubridge = { 104 enable = lib.mkEnableOption ''uBridge support''; 105 package = lib.mkPackageOption pkgs "ubridge" { }; 106 }; 107 108 vpcs = { 109 enable = lib.mkEnableOption ''VPCS support''; 110 package = lib.mkPackageOption pkgs "vpcs" { }; 111 }; 112 }; 113 }; 114 115 config = 116 let 117 flags = { 118 enableDocker = config.virtualisation.docker.enable; 119 enableLibvirtd = config.virtualisation.libvirtd.enable; 120 }; 121 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 ]; 142 143 users.groups.gns3 = { }; 144 145 users.groups.ubridge = lib.mkIf cfg.ubridge.enable { }; 146 147 users.users.gns3 = { 148 group = "gns3"; 149 isSystemUser = true; 150 }; 151 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 }; 159 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 ]; 187 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"; 202 203 after = [ 204 "network.target" 205 "network-online.target" 206 ]; 207 wantedBy = [ "multi-user.target" ]; 208 wants = [ "network-online.target" ]; 209 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 215 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 ''; 223 224 path = lib.optional flags.enableLibvirtd pkgs.qemu; 225 226 reloadTriggers = [ configFile ]; 227 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"; 251 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; 260 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 }; 299}