2024-01-02 11:29:13 +00:00
|
|
|
{ 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 = {
|
2024-04-21 15:54:59 +00:00
|
|
|
enable = lib.mkEnableOption "GNS3 Server daemon";
|
2024-01-02 11:29:13 +00:00
|
|
|
|
|
|
|
package = lib.mkPackageOptionMD pkgs "gns3-server" { };
|
|
|
|
|
|
|
|
auth = {
|
2024-04-21 15:54:59 +00:00
|
|
|
enable = lib.mkEnableOption "password based HTTP authentication to access the GNS3 Server";
|
2024-01-02 11:29:13 +00:00
|
|
|
|
|
|
|
user = lib.mkOption {
|
|
|
|
type = lib.types.nullOr lib.types.str;
|
|
|
|
default = null;
|
|
|
|
example = "gns3";
|
2024-04-21 15:54:59 +00:00
|
|
|
description = ''Username used to access the GNS3 Server.'';
|
2024-01-02 11:29:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
passwordFile = lib.mkOption {
|
|
|
|
type = lib.types.nullOr lib.types.path;
|
|
|
|
default = null;
|
|
|
|
example = "/run/secrets/gns3-server-password";
|
2024-04-21 15:54:59 +00:00
|
|
|
description = ''
|
2024-01-02 11:29:13 +00:00
|
|
|
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; };
|
2024-04-21 15:54:59 +00:00
|
|
|
description = ''
|
2024-01-02 11:29:13 +00:00
|
|
|
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";
|
2024-04-21 15:54:59 +00:00
|
|
|
description = ''Path of the file GNS3 Server should log to.'';
|
2024-01-02 11:29:13 +00:00
|
|
|
};
|
|
|
|
|
2024-04-21 15:54:59 +00:00
|
|
|
debug = lib.mkEnableOption "debug logging";
|
2024-01-02 11:29:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ssl = {
|
2024-04-21 15:54:59 +00:00
|
|
|
enable = lib.mkEnableOption "SSL encryption";
|
2024-01-02 11:29:13 +00:00
|
|
|
|
|
|
|
certFile = lib.mkOption {
|
|
|
|
type = lib.types.nullOr lib.types.path;
|
|
|
|
default = null;
|
|
|
|
example = "/var/lib/gns3/ssl/server.pem";
|
2024-04-21 15:54:59 +00:00
|
|
|
description = ''
|
2024-01-02 11:29:13 +00:00
|
|
|
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";
|
2024-04-21 15:54:59 +00:00
|
|
|
description = "Private key file for the certificate.";
|
2024-01-02 11:29:13 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
dynamips = {
|
2024-04-21 15:54:59 +00:00
|
|
|
enable = lib.mkEnableOption ''Whether to enable Dynamips support.'';
|
2024-01-02 11:29:13 +00:00
|
|
|
package = lib.mkPackageOptionMD pkgs "dynamips" { };
|
|
|
|
};
|
|
|
|
|
|
|
|
ubridge = {
|
2024-04-21 15:54:59 +00:00
|
|
|
enable = lib.mkEnableOption ''Whether to enable uBridge support.'';
|
2024-01-02 11:29:13 +00:00
|
|
|
package = lib.mkPackageOptionMD pkgs "ubridge" { };
|
|
|
|
};
|
|
|
|
|
|
|
|
vpcs = {
|
2024-04-21 15:54:59 +00:00
|
|
|
enable = lib.mkEnableOption ''Whether to enable VPCS support.'';
|
2024-01-02 11:29:13 +00:00
|
|
|
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";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|