Merge pull request #252790 from anthonyroussel/gns3-nixos-module

nixos/gns3-server: init
This commit is contained in:
Mario Rodas 2023-12-23 09:27:39 -05:00 committed by GitHub
commit 2ba8c30b4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 360 additions and 3 deletions

View file

@ -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.

View file

@ -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

View file

@ -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;
};
}
```

View file

@ -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 <https://docs.gns3.com/docs/using-gns3/administration/gns3-server-configuration-file/>
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";
};
};
};
}

View file

@ -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 {};

View file

@ -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)
'';
})

View file

@ -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; {