nixpkgs/nixos/modules/services/networking/gns3-server.nix
2024-09-23 20:46:18 +02:00

282 lines
8.7 KiB
Nix

{ 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 "GNS3 Server daemon";
package = lib.mkPackageOption pkgs "gns3-server" { };
auth = {
enable = lib.mkEnableOption "password based HTTP authentication to access the GNS3 Server";
user = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "gns3";
description = ''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 = ''
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 = ''
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 = ''Path of the file GNS3 Server should log to.'';
};
debug = lib.mkEnableOption "debug logging";
};
ssl = {
enable = lib.mkEnableOption "SSL encryption";
certFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/var/lib/gns3/ssl/server.pem";
description = ''
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 = "Private key file for the certificate.";
};
};
dynamips = {
enable = lib.mkEnableOption ''Dynamips support'';
package = lib.mkPackageOption pkgs "dynamips" { };
};
ubridge = {
enable = lib.mkEnableOption ''uBridge support'';
package = lib.mkPackageOption pkgs "ubridge" { };
};
vpcs = {
enable = lib.mkEnableOption ''VPCS support'';
package = lib.mkPackageOption 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.gns3 = { };
users.groups.ubridge = lib.mkIf cfg.ubridge.enable { };
users.users.gns3 = {
group = "gns3";
isSystemUser = true;
};
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 "/run/wrappers/bin/ubridge";
})
(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";
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";
# Required for ubridge integration to work
#
# GNS3 needs to run SUID binaries (ubridge)
# but NoNewPrivileges breaks execution of SUID binaries
DynamicUser = false;
NoNewPrivileges = false;
RestrictSUIDSGID = false;
PrivateUsers = false;
# Hardening
DeviceAllow = [
# ubridge needs access to tun/tap devices
"/dev/net/tap rw"
"/dev/net/tun rw"
] ++ lib.optionals flags.enableLibvirtd [
"/dev/kvm"
];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateTmp = 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;
UMask = "0022";
};
};
};
}