diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index 5a1ab55209e0..c2c8f8acab65 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -24,6 +24,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable). +- [GNS3](https://www.gns3.com/), a network software emulator. Available as [services.gns3-server](#opt-services.gns3-server.enable). + - [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable). The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been marked deprecated and will be dropped after 24.05 due to lack of maintenance of the anki-sync-server softwares. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0560caee6704..4a4ec9ee73f5 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -946,6 +946,7 @@ ./services/networking/ghostunnel.nix ./services/networking/git-daemon.nix ./services/networking/globalprotect-vpn.nix + ./services/networking/gns3-server.nix ./services/networking/gnunet.nix ./services/networking/go-autoconfig.nix ./services/networking/go-neb.nix diff --git a/nixos/modules/services/networking/gns3-server.md b/nixos/modules/services/networking/gns3-server.md new file mode 100644 index 000000000000..9320d914fbd3 --- /dev/null +++ b/nixos/modules/services/networking/gns3-server.md @@ -0,0 +1,31 @@ +# GNS3 Server {#module-services-gns3-server} + +[GNS3](https://www.gns3.com/), a network software emulator. + +## Basic Usage {#module-services-gns3-server-basic-usage} + +A minimal configuration looks like this: + +```nix +{ + services.gns3-server = { + enable = true; + + auth = { + enable = true; + user = "gns3"; + passwordFile = "/var/lib/secrets/gns3_password"; + }; + + ssl = { + enable = true; + certFile = "/var/lib/gns3/ssl/cert.pem"; + keyFile = "/var/lib/gns3/ssl/key.pem"; + }; + + dynamips.enable = true; + ubridge.enable = true; + vpcs.enable = true; + }; +} +``` diff --git a/nixos/modules/services/networking/gns3-server.nix b/nixos/modules/services/networking/gns3-server.nix new file mode 100644 index 000000000000..25583765de67 --- /dev/null +++ b/nixos/modules/services/networking/gns3-server.nix @@ -0,0 +1,263 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.gns3-server; + + settingsFormat = pkgs.formats.ini { }; + configFile = settingsFormat.generate "gns3-server.conf" cfg.settings; + +in { + meta = { + doc = ./gns3-server.md; + maintainers = [ lib.maintainers.anthonyroussel ]; + }; + + options = { + services.gns3-server = { + enable = lib.mkEnableOption (lib.mdDoc "GNS3 Server daemon"); + + package = lib.mkPackageOptionMD pkgs "gns3-server" { }; + + auth = { + enable = lib.mkEnableOption (lib.mdDoc "password based HTTP authentication to access the GNS3 Server"); + + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "gns3"; + description = lib.mdDoc ''Username used to access the GNS3 Server.''; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/secrets/gns3-server-password"; + description = lib.mdDoc '' + A file containing the password to access the GNS3 Server. + + ::: {.warning} + This should be a string, not a nix path, since nix paths + are copied into the world-readable nix store. + ::: + ''; + }; + }; + + settings = lib.mkOption { + type = lib.types.submodule { freeformType = settingsFormat.type; }; + default = {}; + example = { host = "127.0.0.1"; port = 3080; }; + description = lib.mdDoc '' + The global options in `config` file in ini format. + + Refer to + for all available options. + ''; + }; + + log = { + file = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = "/var/log/gns3/server.log"; + description = lib.mdDoc ''Path of the file GNS3 Server should log to.''; + }; + + debug = lib.mkEnableOption (lib.mdDoc "debug logging"); + }; + + ssl = { + enable = lib.mkEnableOption (lib.mdDoc "SSL encryption"); + + certFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/var/lib/gns3/ssl/server.pem"; + description = lib.mdDoc '' + Path to the SSL certificate file. This certificate will + be offered to, and may be verified by, clients. + ''; + }; + + keyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/var/lib/gns3/ssl/server.key"; + description = lib.mdDoc "Private key file for the certificate."; + }; + }; + + dynamips = { + enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable Dynamips support.''); + package = lib.mkPackageOptionMD pkgs "dynamips" { }; + }; + + ubridge = { + enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable uBridge support.''); + package = lib.mkPackageOptionMD pkgs "ubridge" { }; + }; + + vpcs = { + enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable VPCS support.''); + package = lib.mkPackageOptionMD pkgs "vpcs" { }; + }; + }; + }; + + config = let + flags = { + enableDocker = config.virtualisation.docker.enable; + enableLibvirtd = config.virtualisation.libvirtd.enable; + }; + + in lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.ssl.enable -> cfg.ssl.certFile != null; + message = "Please provide a certificate to use for SSL encryption."; + } + { + assertion = cfg.ssl.enable -> cfg.ssl.keyFile != null; + message = "Please provide a private key to use for SSL encryption."; + } + { + assertion = cfg.auth.enable -> cfg.auth.user != null; + message = "Please provide a username to use for HTTP authentication."; + } + { + assertion = cfg.auth.enable -> cfg.auth.passwordFile != null; + message = "Please provide a password file to use for HTTP authentication."; + } + ]; + + users.groups.ubridge = lib.mkIf cfg.ubridge.enable { }; + + security.wrappers.ubridge = lib.mkIf cfg.ubridge.enable { + capabilities = "cap_net_raw,cap_net_admin=eip"; + group = "ubridge"; + owner = "root"; + permissions = "u=rwx,g=rx,o=r"; + source = lib.getExe cfg.ubridge.package; + }; + + services.gns3-server.settings = lib.mkMerge [ + { + Server = { + appliances_path = lib.mkDefault "/var/lib/gns3/appliances"; + configs_path = lib.mkDefault "/var/lib/gns3/configs"; + images_path = lib.mkDefault "/var/lib/gns3/images"; + projects_path = lib.mkDefault "/var/lib/gns3/projects"; + symbols_path = lib.mkDefault "/var/lib/gns3/symbols"; + }; + } + (lib.mkIf (cfg.ubridge.enable) { + Server.ubridge_path = lib.mkDefault (lib.getExe cfg.ubridge.package); + }) + (lib.mkIf (cfg.auth.enable) { + Server = { + auth = lib.mkDefault (lib.boolToString cfg.auth.enable); + user = lib.mkDefault cfg.auth.user; + password = lib.mkDefault "@AUTH_PASSWORD@"; + }; + }) + (lib.mkIf (cfg.vpcs.enable) { + VPCS.vpcs_path = lib.mkDefault (lib.getExe cfg.vpcs.package); + }) + (lib.mkIf (cfg.dynamips.enable) { + Dynamips.dynamips_path = lib.mkDefault (lib.getExe cfg.dynamips.package); + }) + ]; + + systemd.services.gns3-server = let + commandArgs = lib.cli.toGNUCommandLineShell { } { + config = "/etc/gns3/gns3_server.conf"; + pid = "/run/gns3/server.pid"; + log = cfg.log.file; + ssl = cfg.ssl.enable; + # These are implicitly not set if `null` + certfile = cfg.ssl.certFile; + certkey = cfg.ssl.keyFile; + }; + in + { + description = "GNS3 Server"; + + after = [ "network.target" "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + + # configFile cannot be stored in RuntimeDirectory, because GNS3 + # uses the `--config` base path to stores supplementary configuration files at runtime. + # + preStart = '' + install -m660 ${configFile} /etc/gns3/gns3_server.conf + + ${lib.optionalString cfg.auth.enable '' + ${pkgs.replace-secret}/bin/replace-secret \ + '@AUTH_PASSWORD@' \ + "''${CREDENTIALS_DIRECTORY}/AUTH_PASSWORD" \ + /etc/gns3/gns3_server.conf + ''} + ''; + + path = lib.optional flags.enableLibvirtd pkgs.qemu; + + reloadTriggers = [ configFile ]; + + serviceConfig = { + ConfigurationDirectory = "gns3"; + ConfigurationDirectoryMode = "0750"; + DynamicUser = true; + Environment = "HOME=%S/gns3"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = "${lib.getExe cfg.package} ${commandArgs}"; + Group = "gns3"; + LimitNOFILE = 16384; + LoadCredential = lib.mkIf cfg.auth.enable [ "AUTH_PASSWORD:${cfg.auth.passwordFile}" ]; + LogsDirectory = "gns3"; + LogsDirectoryMode = "0750"; + PIDFile = "/run/gns3/server.pid"; + Restart = "on-failure"; + RestartSec = 5; + RuntimeDirectory = "gns3"; + StateDirectory = "gns3"; + StateDirectoryMode = "0750"; + SupplementaryGroups = lib.optional flags.enableDocker "docker" + ++ lib.optional flags.enableLibvirtd "libvirtd" + ++ lib.optional cfg.ubridge.enable "ubridge"; + User = "gns3"; + WorkingDirectory = "%S/gns3"; + + # Hardening + DeviceAllow = lib.optional flags.enableLibvirtd "/dev/kvm"; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateUsers = true; + # Don't restrict ProcSubset because python3Packages.psutil requires read access to /proc/stat + # ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + "AF_PACKET" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + UMask = "0077"; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f09c79e782b8..0ef5c4d8d3cb 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -342,6 +342,7 @@ in { gnome-extensions = handleTest ./gnome-extensions.nix {}; gnome-flashback = handleTest ./gnome-flashback.nix {}; gnome-xorg = handleTest ./gnome-xorg.nix {}; + gns3-server = handleTest ./gns3-server.nix {}; gnupg = handleTest ./gnupg.nix {}; go-neb = handleTest ./go-neb.nix {}; gobgpd = handleTest ./gobgpd.nix {}; diff --git a/nixos/tests/gns3-server.nix b/nixos/tests/gns3-server.nix new file mode 100644 index 000000000000..e37d751f5f64 --- /dev/null +++ b/nixos/tests/gns3-server.nix @@ -0,0 +1,55 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "gns3-server"; + meta.maintainers = [ lib.maintainers.anthonyroussel ]; + + nodes.machine = + { ... }: + let + tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } '' + openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 365 \ + -subj '/CN=localhost' + install -D -t $out key.pem cert.pem + ''; + in { + services.gns3-server = { + enable = true; + auth = { + enable = true; + user = "user"; + passwordFile = pkgs.writeText "gns3-auth-password-file" "password"; + }; + ssl = { + enable = true; + certFile = "${tls-cert}/cert.pem"; + keyFile = "${tls-cert}/key.pem"; + }; + dynamips.enable = true; + ubridge.enable = true; + vpcs.enable = true; + }; + + security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ]; + }; + + testScript = let + createProject = pkgs.writeText "createProject.json" (builtins.toJSON { + name = "test_project"; + }); + in + '' + start_all() + + machine.wait_for_unit("gns3-server.service") + machine.wait_for_open_port(3080) + + with subtest("server is listening"): + machine.succeed("curl -sSfL -u user:password https://localhost:3080/v2/version") + + with subtest("create dummy project"): + machine.succeed("curl -sSfL -u user:password https://localhost:3080/v2/projects -d @${createProject}") + + with subtest("logging works"): + log_path = "/var/log/gns3/server.log" + machine.wait_for_file(log_path) + ''; +}) diff --git a/pkgs/applications/networking/gns3/server.nix b/pkgs/applications/networking/gns3/server.nix index 9253412aac03..ae792037e453 100644 --- a/pkgs/applications/networking/gns3/server.nix +++ b/pkgs/applications/networking/gns3/server.nix @@ -8,6 +8,7 @@ , fetchFromGitHub , pkgsStatic , stdenv +, nixosTests , testers , gns3-server }: @@ -75,9 +76,12 @@ python3.pkgs.buildPythonApplication { "--reruns 3" ]; - passthru.tests.version = testers.testVersion { - package = gns3-server; - command = "${lib.getExe gns3-server} --version"; + passthru.tests = { + inherit (nixosTests) gns3-server; + version = testers.testVersion { + package = gns3-server; + command = "${lib.getExe gns3-server} --version"; + }; }; meta = with lib; {