Use Pydantic to validate the server config file.

This commit is contained in:
grossmj committed 2021-04-12 17:02:23 +09:30
1 parent 478119b40d
commit 30ebae207f
61 files changed
+871 -695

No files matched your search

+3 -6
View File
@@ -83,8 +83,7 @@ def compute_version() -> dict:
Retrieve the server version number.
"""
config = Config.instance()
local_server = config.get_section_config("Server").getboolean("local", False)
local_server = Config.instance().settings.Server.local
return {"version": __version__, "local": local_server}
@@ -153,8 +152,7 @@ async def create_qemu_image(image_data: schemas.QemuImageCreate):
"""
if os.path.isabs(image_data.path):
config = Config.instance()
if config.get_section_config("Server").getboolean("local", False) is False:
if Config.instance().settings.Server.local is False:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
await Qemu.instance().create_disk(image_data.qemu_img, image_data.path, jsonable_encoder(image_data, exclude_unset=True))
@@ -169,8 +167,7 @@ async def update_qemu_image(image_data: schemas.QemuImageUpdate):
"""
if os.path.isabs(image_data.path):
config = Config.instance()
if config.get_section_config("Server").getboolean("local", False) is False:
if Config.instance().settings.Server.local is False:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
if image_data.extend:
+1 -6
View File
@@ -148,12 +148,7 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)):
"""
qemu_manager = Qemu.instance()
hardware_accel = qemu_manager.config.get_section_config("Qemu").getboolean("enable_hardware_acceleration", True)
if sys.platform.startswith("linux"):
# the enable_kvm option was used before version 2.0 and has priority
enable_kvm = qemu_manager.config.get_section_config("Qemu").getboolean("enable_kvm")
if enable_kvm is not None:
hardware_accel = enable_kvm
hardware_accel = qemu_manager.config.settings.Qemu.enable_hardware_acceleration
if hardware_accel and "-no-kvm" not in node.options and "-no-hax" not in node.options:
pm = ProjectManager.instance()
if pm.check_hardware_virtualization(node) is False:
@@ -43,8 +43,7 @@ async def shutdown():
Shutdown the local server
"""
config = Config.instance()
if config.get_section_config("Server").getboolean("local", False) is False:
if Config.instance().settings.Server.local is False:
raise ControllerForbiddenError("You can only stop a local server")
log.info("Start shutting down the server")
@@ -76,8 +75,7 @@ def get_version():
Return the server version number.
"""
config = Config.instance()
local_server = config.get_section_config("Server").getboolean("local", False)
local_server = Config.instance().settings.Server.local
return {"version": __version__, "local": local_server}
+3 -6
View File
@@ -183,9 +183,8 @@ async def load_project(path: str = Body(..., embed=True)):
"""
controller = Controller.instance()
config = Config.instance()
dot_gns3_file = path
if config.get_section_config("Server").getboolean("local", False) is False:
if Config.instance().settings.Server.local is False:
log.error("Cannot load '{}' because the server has not been started with the '--local' parameter".format(dot_gns3_file))
raise ControllerForbiddenError("Cannot load project when server is not local")
project = await controller.load_project(dot_gns3_file,)
@@ -313,8 +312,7 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path
"""
controller = Controller.instance()
config = Config.instance()
if not config.get_section_config("Server").getboolean("local", False):
if Config.instance().settings.Server.local is False:
raise ControllerForbiddenError("The server is not local")
# We write the content to a temporary location and after we extract it all.
@@ -353,8 +351,7 @@ async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Pro
"""
if project_data.path:
config = Config.instance()
if config.get_section_config("Server").getboolean("local", False) is False:
if Config.instance().settings.Server.local is False:
raise ControllerForbiddenError("The server is not a local server")
location = project_data.path
else:
+1 -2
View File
@@ -418,7 +418,6 @@ class BaseManager:
return ""
orig_path = path
server_config = self.config.get_section_config("Server")
img_directory = self.get_images_directory()
valid_directory_prefices = images_directories(self._NODE_TYPE)
if extra_dir:
@@ -445,7 +444,7 @@ class BaseManager:
raise ImageMissingError(orig_path)
# For local server we allow using absolute path outside image directory
if server_config.getboolean("local", False) is True:
if Config.instance().settings.Server.local is True:
log.debug("Searching for '{}'".format(orig_path))
path = force_unix_path(path)
if os.path.exists(path):
+4 -7
View File
@@ -373,9 +373,8 @@ class BaseNode:
Returns the VNC console port range.
"""
server_config = self._manager.config.get_section_config("Server")
vnc_console_start_port_range = server_config.getint("vnc_console_start_port_range", 5900)
vnc_console_end_port_range = server_config.getint("vnc_console_end_port_range", 10000)
vnc_console_start_port_range = self._manager.config.settings.Server.vnc_console_start_port_range
vnc_console_end_port_range = self._manager.config.settings.Server.vnc_console_end_port_range
if not 5900 <= vnc_console_start_port_range <= 65535:
raise NodeError("The VNC console start port range must be between 5900 and 65535")
@@ -685,8 +684,7 @@ class BaseNode:
:returns: path to uBridge
"""
path = self._manager.config.get_section_config("Server").get("ubridge_path", "ubridge")
path = shutil.which(path)
path = shutil.which(self._manager.config.settings.Server.ubridge_path)
return path
async def _ubridge_send(self, command):
@@ -721,8 +719,7 @@ class BaseNode:
if require_privileged_access and not self._manager.has_privileged_access(self.ubridge_path):
raise NodeError("uBridge requires root access or the capability to interact with network adapters")
server_config = self._manager.config.get_section_config("Server")
server_host = server_config.get("host")
server_host = self._manager.config.settings.Server.host
if not self.ubridge:
self._ubridge_hypervisor = Hypervisor(self._project, self.ubridge_path, self.working_dir, server_host)
log.info("Starting new uBridge hypervisor {}:{}".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
+6 -2
View File
@@ -36,12 +36,16 @@ class Nat(Cloud):
def __init__(self, name, node_id, project, manager, ports=None):
if sys.platform.startswith("linux"):
nat_interface = Config.instance().get_section_config("Server").get("default_nat_interface", "virbr0")
nat_interface = Config.instance().settings.Server.default_nat_interface
if not nat_interface:
nat_interface = "virbr0"
if nat_interface not in [interface["name"] for interface in gns3server.utils.interfaces.interfaces()]:
raise NodeError("NAT interface {} is missing, please install libvirt".format(nat_interface))
interface = nat_interface
else:
nat_interface = Config.instance().get_section_config("Server").get("default_nat_interface", "vmnet8")
nat_interface = Config.instance().settings.Server.default_nat_interface
if not nat_interface:
nat_interface = "vmnet8"
interfaces = list(filter(lambda x: nat_interface in x.lower(),
[interface["name"] for interface in gns3server.utils.interfaces.interfaces()]))
if not len(interfaces):
+5 -6
View File
@@ -248,7 +248,7 @@ class Dynamips(BaseManager):
def find_dynamips(self):
# look for Dynamips
dynamips_path = self.config.get_section_config("Dynamips").get("dynamips_path", "dynamips")
dynamips_path = self.config.settings.Dynamips.dynamips_path
if not os.path.isabs(dynamips_path):
dynamips_path = shutil.which(dynamips_path)
@@ -279,8 +279,7 @@ class Dynamips(BaseManager):
# FIXME: hypervisor should always listen to 127.0.0.1
# See https://github.com/GNS3/dynamips/issues/62
server_config = self.config.get_section_config("Server")
server_host = server_config.get("host")
server_host = self.config.settings.Server.host
try:
info = socket.getaddrinfo(server_host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
@@ -310,7 +309,7 @@ class Dynamips(BaseManager):
async def ghost_ios_support(self, vm):
ghost_ios_support = self.config.get_section_config("Dynamips").getboolean("ghost_ios_support", True)
ghost_ios_support = self.config.settings.Dynamips.ghost_ios_support
if ghost_ios_support:
async with Dynamips._ghost_ios_lock:
try:
@@ -483,11 +482,11 @@ class Dynamips(BaseManager):
except IndexError:
raise DynamipsError("WIC slot {} doesn't exist on this router".format(wic_slot_id))
mmap_support = self.config.get_section_config("Dynamips").getboolean("mmap_support", True)
mmap_support = self.config.settings.Dynamips.mmap_support
if mmap_support is False:
await vm.set_mmap(False)
sparse_memory_support = self.config.get_section_config("Dynamips").getboolean("sparse_memory_support", True)
sparse_memory_support = self.config.settings.Dynamips.sparse_memory_support
if sparse_memory_support is False:
await vm.set_sparsemem(False)
+2 -5
View File
@@ -95,9 +95,6 @@ class IOUVM(BaseNode):
self._application_id = application_id
self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
def _config(self):
return self._manager.config.get_section_config("IOU")
def _nvram_changed(self, path):
"""
Called when the NVRAM file has changed
@@ -248,7 +245,7 @@ class IOUVM(BaseNode):
:returns: path to IOURC
"""
iourc_path = self._config().get("iourc_path")
iourc_path = self._manager.config.settings.IOU.iourc_path
if not iourc_path:
# look for the iourc file in the temporary dir.
path = os.path.join(self.temporary_directory, "iourc")
@@ -401,7 +398,7 @@ class IOUVM(BaseNode):
try:
# we allow license check to be disabled server wide
server_wide_license_check = self._config().getboolean("license_check", True)
server_wide_license_check = self._manager.config.settings.IOU.license_check
except ValueError:
raise IOUError("Invalid licence check setting")
+5 -8
View File
@@ -43,15 +43,13 @@ class PortManager:
self._used_tcp_ports = set()
self._used_udp_ports = set()
server_config = Config.instance().get_section_config("Server")
console_start_port_range = server_config.getint("console_start_port_range", 5000)
console_end_port_range = server_config.getint("console_end_port_range", 10000)
console_start_port_range = Config.instance().settings.Server.console_start_port_range
console_end_port_range = Config.instance().settings.Server.console_end_port_range
self._console_port_range = (console_start_port_range, console_end_port_range)
log.debug(f"Console port range is {console_start_port_range}-{console_end_port_range}")
udp_start_port_range = server_config.getint("udp_start_port_range", 20000)
udp_end_port_range = server_config.getint("udp_end_port_range", 30000)
udp_start_port_range = Config.instance().settings.Server.udp_start_port_range
udp_end_port_range = Config.instance().settings.Server.udp_end_port_range
self._udp_port_range = (udp_start_port_range, udp_end_port_range)
log.debug(f"UDP port range is {udp_start_port_range}-{udp_end_port_range}")
@@ -86,8 +84,7 @@ class PortManager:
Bind console host to 0.0.0.0 if remote connections are allowed.
"""
server_config = Config.instance().get_section_config("Server")
remote_console_connections = server_config.getboolean("allow_remote_console")
remote_console_connections = Config.instance().settings.Server.allow_remote_console
if remote_console_connections:
log.warning("Remote console connections are allowed")
self._console_host = "0.0.0.0"
+1 -5
View File
@@ -85,13 +85,9 @@ class Project:
"variables": self._variables
}
def _config(self):
return Config.instance().get_section_config("Server")
def is_local(self):
return self._config().getboolean("local", False)
return Config.instance().settings.Server.local
@property
def id(self):
+7 -16
View File
@@ -73,9 +73,9 @@ class QemuVM(BaseNode):
def __init__(self, name, node_id, project, manager, linked_clone=True, qemu_path=None, console=None, console_type="telnet", aux=None, aux_type="none", platform=None):
super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone, aux=aux, aux_type=aux_type, wrap_console=True, wrap_aux=True)
server_config = manager.config.get_section_config("Server")
self._host = server_config.get("host", "127.0.0.1")
self._monitor_host = server_config.get("monitor_host", "127.0.0.1")
self._host = manager.config.settings.Server.host
self._monitor_host = manager.config.settings.Qemu.monitor_host
self._process = None
self._cpulimit_process = None
self._monitor = None
@@ -1055,7 +1055,7 @@ class QemuVM(BaseNode):
await self.resume()
return
if self._manager.config.get_section_config("Qemu").getboolean("monitor", True):
if self._manager.config.settings.Qemu.enable_monitor:
try:
info = socket.getaddrinfo(self._monitor_host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
if not info:
@@ -2112,17 +2112,8 @@ class QemuVM(BaseNode):
:returns: Boolean True if we need to enable hardware acceleration
"""
enable_hardware_accel = self.manager.config.get_section_config("Qemu").getboolean("enable_hardware_acceleration", True)
require_hardware_accel = self.manager.config.get_section_config("Qemu").getboolean("require_hardware_acceleration", True)
if sys.platform.startswith("linux"):
# compatibility: these options were used before version 2.0 and have priority
enable_kvm = self.manager.config.get_section_config("Qemu").getboolean("enable_kvm")
if enable_kvm is not None:
enable_hardware_accel = enable_kvm
require_kvm = self.manager.config.get_section_config("Qemu").getboolean("require_kvm")
if require_kvm is not None:
require_hardware_accel = require_kvm
enable_hardware_accel = self.manager.config.settings.Qemu.enable_hardware_acceleration
require_hardware_accel = self.manager.config.settings.Qemu.require_hardware_acceleration
if enable_hardware_accel and "-no-kvm" not in options and "-no-hax" not in options:
# Turn OFF hardware acceleration for non x86 architectures
if sys.platform.startswith("win"):
@@ -2137,7 +2128,7 @@ class QemuVM(BaseNode):
if sys.platform.startswith("linux") and not os.path.exists("/dev/kvm"):
if require_hardware_accel:
raise QemuError("KVM acceleration cannot be used (/dev/kvm doesn't exist). It is possible to turn off KVM support in the gns3_server.conf by adding enable_kvm = false to the [Qemu] section.")
raise QemuError("KVM acceleration cannot be used (/dev/kvm doesn't exist). It is possible to turn off KVM support in the gns3_server.conf by adding enable_hardware_acceleration = false to the [Qemu] section.")
else:
return False
elif sys.platform.startswith("win"):
+1 -1
View File
@@ -57,7 +57,7 @@ class VirtualBox(BaseManager):
def find_vboxmanage(self):
# look for VBoxManage
vboxmanage_path = self.config.get_section_config("VirtualBox").get("vboxmanage_path")
vboxmanage_path = self.config.settings.VirtualBox.vboxmanage_path
if vboxmanage_path:
if not os.path.isabs(vboxmanage_path):
vboxmanage_path = shutil.which(vboxmanage_path)
+3 -3
View File
@@ -91,7 +91,7 @@ class VMware(BaseManager):
"""
# look for vmrun
vmrun_path = self.config.get_section_config("VMware").get("vmrun_path")
vmrun_path = self.config.settings.VMware.vmrun_path
if not vmrun_path:
if sys.platform.startswith("win"):
vmrun_path = shutil.which("vmrun")
@@ -309,8 +309,8 @@ class VMware(BaseManager):
def is_managed_vmnet(self, vmnet):
self._vmnet_start_range = self.config.get_section_config("VMware").getint("vmnet_start_range", self._vmnet_start_range)
self._vmnet_end_range = self.config.get_section_config("VMware").getint("vmnet_end_range", self._vmnet_end_range)
self._vmnet_start_range = self.config.settings.VMware.vmnet_start_range
self._vmnet_end_range = self.config.settings.VMware.vmnet_end_range
match = re.search(r"vmnet([0-9]+)$", vmnet, re.IGNORECASE)
if match:
vmnet_number = match.group(1)
+1 -1
View File
@@ -336,7 +336,7 @@ class VMwareVM(BaseNode):
# special case on OSX, we cannot bind VMnet interfaces using the libpcap
await self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet, interface=vmnet_interface))
else:
block_host_traffic = self.manager.config.get_section_config("VMware").getboolean("block_host_traffic", False)
block_host_traffic = self.manager.config.VMware.block_host_traffic
await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
if isinstance(nio, NIOUDP):
+1 -1
View File
@@ -138,7 +138,7 @@ class VPCSVM(BaseNode):
:returns: path to VPCS
"""
vpcs_path = self._manager.config.get_section_config("VPCS").get("vpcs_path", "vpcs")
vpcs_path = self._manager.config.settings.VPCS.vpcs_path
if not os.path.isabs(vpcs_path):
vpcs_path = shutil.which(vpcs_path)
return vpcs_path
+81 -106
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
# Copyright (C) 2021 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,14 +16,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Reads the configuration file and store the settings for the controller & compute.
Reads the configuration file and store the settings for the server.
"""
import sys
import os
import shutil
import secrets
import configparser
from pydantic import ValidationError
from .schemas import ServerConfig
from .version import __version_info__
from .utils.file_watcher import FileWatcher
@@ -32,18 +35,19 @@ log = logging.getLogger(__name__)
class Config:
"""
Configuration file management using configparser.
:param files: Array of configuration files (optional)
:param profile: Profile settings (default use standard settings file)
:param profile: Profile settings (default use standard config file)
"""
def __init__(self, files=None, profile=None):
self._settings = None
self._files = files
self._profile = profile
if files and len(files):
directory_name = os.path.dirname(files[0])
if not directory_name or directory_name == "":
@@ -79,15 +83,6 @@ class Config:
versioned_user_dir = os.path.join(appdata, appname, version)
server_filename = "gns3_server.ini"
controller_filename = "gns3_controller.ini"
# move gns3_controller.conf to gns3_controller.ini (file was renamed in 2.2.0 on Windows)
old_controller_filename = os.path.join(legacy_user_dir, "gns3_controller.conf")
if os.path.exists(old_controller_filename):
try:
shutil.copyfile(old_controller_filename, os.path.join(legacy_user_dir, controller_filename))
except OSError as e:
log.error("Cannot move old controller configuration file: {}".format(e))
if self._files is None and not hasattr(sys, "_called_from_test"):
self._files = [os.path.join(os.getcwd(), server_filename),
@@ -106,7 +101,6 @@ class Config:
home = os.path.expanduser("~")
server_filename = "gns3_server.conf"
controller_filename = "gns3_controller.conf"
if self._profile:
legacy_user_dir = os.path.join(home, ".config", appname, "profiles", self._profile)
@@ -128,7 +122,7 @@ class Config:
if self._main_config_file is None:
# TODO: migrate versioned config file from a previous version of GNS3 (for instance 2.2 -> 2.3) + support profiles
# TODO: migrate versioned config file from a previous version of GNS3 (for instance 2.2 -> 3.0) + support profiles
# migrate post version 2.2.0 config files if they exist
os.makedirs(versioned_user_dir, exist_ok=True)
try:
@@ -137,12 +131,6 @@ class Config:
new_server_config = os.path.join(versioned_user_dir, server_filename)
if not os.path.exists(new_server_config) and os.path.exists(old_server_config):
shutil.copyfile(old_server_config, new_server_config)
# migrate the controller config file
old_controller_config = os.path.join(legacy_user_dir, controller_filename)
new_controller_config = os.path.join(versioned_user_dir, controller_filename)
if not os.path.exists(new_controller_config) and os.path.exists(old_controller_config):
shutil.copyfile(old_controller_config, os.path.join(versioned_user_dir, new_controller_config))
except OSError as e:
log.error("Cannot migrate old config files: {}".format(e))
@@ -155,6 +143,16 @@ class Config:
self.clear()
self._watch_config_file()
@property
def settings(self) -> ServerConfig:
"""
Return the settings.
"""
if self._settings is None:
return ServerConfig()
return self._settings
def listen_for_config_changes(self, callback):
"""
Call the callback when the configuration file change
@@ -170,20 +168,17 @@ class Config:
@property
def config_dir(self):
"""
Return the directory where the configuration file is located.
"""
return os.path.dirname(self._main_config_file)
@property
def controller_config(self):
if sys.platform.startswith("win"):
controller_config_filename = "gns3_controller.ini"
else:
controller_config_filename = "gns3_controller.conf"
return os.path.join(self.config_dir, controller_config_filename)
@property
def server_config(self):
"""
Return the server configuration file path.
"""
if sys.platform.startswith("win"):
server_config_filename = "gns3_server.ini"
@@ -196,21 +191,24 @@ class Config:
Restart with a clean config
"""
self._config = configparser.ConfigParser(interpolation=None)
# Override config from command line even if we modify the config file and live reload it.
self._override_config = {}
self.read_config()
def _watch_config_file(self):
"""
Add config files to be monitored for changes.
"""
for file in self._files:
if os.path.exists(file):
self._watched_files[file] = FileWatcher(file, self._config_file_change)
def _config_file_change(self, path):
def _config_file_change(self, file_path):
"""
Callback when a config file has been updated.
"""
log.info(f"'{file_path}' has been updated, reloading the config...")
self.read_config()
for section in self._override_config:
self.set_section_config(section, self._override_config[section])
for callback in self._watch_callback:
callback()
@@ -220,93 +218,70 @@ class Config:
"""
self.read_config()
for section in self._override_config:
self.set_section_config(section, self._override_config[section])
def get_config_files(self):
"""
Return the config files in use.
"""
return self._watched_files
def _load_jwt_secret_key(self):
"""
Load the JWT secret key.
"""
jwt_secret_key_path = os.path.join(self._settings.Server.secrets_dir, "gns3_jwt_secret_key")
if not os.path.exists(jwt_secret_key_path):
log.info(f"No JWT secret key configured, generating one in '{jwt_secret_key_path}'...")
try:
with open(jwt_secret_key_path, "w+", encoding="utf-8") as fd:
fd.write(secrets.token_hex(32))
except OSError as e:
log.error(f"Could not create JWT secret key file '{jwt_secret_key_path}': {e}")
try:
with open(jwt_secret_key_path, encoding="utf-8") as fd:
jwt_secret_key_content = fd.read()
self._settings.Controller.jwt_secret_key = jwt_secret_key_content
except OSError as e:
log.error(f"Could not read JWT secret key file '{jwt_secret_key_path}': {e}")
def _load_secret_files(self):
"""
Load the secret files.
"""
if not self._settings.Server.secrets_dir:
self._settings.Server.secrets_dir = os.path.dirname(self.server_config)
self._load_jwt_secret_key()
def read_config(self):
"""
Read the configuration files.
Read the configuration files and validate the settings.
"""
config = configparser.ConfigParser(interpolation=None)
try:
parsed_files = self._config.read(self._files, encoding="utf-8")
parsed_files = config.read(self._files, encoding="utf-8")
except configparser.Error as e:
log.error("Can't parse configuration file: %s", str(e))
return
if not parsed_files:
log.warning("No configuration file could be found or read")
else:
for file in parsed_files:
log.info("Load configuration file {}".format(file))
self._watched_files[file] = os.stat(file).st_mtime
return
def write_config(self):
"""
Write the server configuration file.
"""
for file in parsed_files:
log.info(f"Load configuration file '{file}'")
self._watched_files[file] = os.stat(file).st_mtime
try:
os.makedirs(os.path.dirname(self.server_config), exist_ok=True)
with open(self.server_config, 'w+') as fd:
self._config.write(fd)
except OSError as e:
log.error("Cannot write server configuration file '{}': {}".format(self.server_config, e))
self._settings = ServerConfig(**config._sections)
except ValidationError as e:
log.error(f"Could not validate config: {e}")
return
def get_default_section(self):
"""
Get the default configuration section.
:returns: configparser section
"""
return self._config["DEFAULT"]
def get_section_config(self, section):
"""
Get a specific configuration section.
Returns the default section if none can be found.
:returns: configparser section
"""
if section not in self._config:
return self._config["DEFAULT"]
return self._config[section]
def set_section_config(self, section, content):
"""
Set a specific configuration section. It's not
dumped on the disk.
:param section: Section name
:param content: A dictionary with section content
"""
if not self._config.has_section(section):
self._config.add_section(section)
for key in content:
if isinstance(content[key], bool):
content[key] = str(content[key]).lower()
self._config.set(section, key, content[key])
self._override_config[section] = content
def set(self, section, key, value):
"""
Set a config value.
It's not dumped on the disk.
If the section doesn't exists the section is created
"""
conf = self.get_section_config(section)
if isinstance(value, bool):
conf[key] = str(value)
else:
conf[key] = value
self.set_section_config(section, conf)
self._load_secret_files()
@staticmethod
def instance(*args, **kwargs):
+77 -79
View File
@@ -59,17 +59,15 @@ class Controller:
self._iou_license_settings = {"iourc_content": "",
"license_check": True}
self._config_loaded = False
self._config_file = Config.instance().controller_config
log.info("Load controller configuration file {}".format(self._config_file))
async def start(self, computes=None):
log.info("Controller is starting")
self.load_base_files()
server_config = Config.instance().get_section_config("Server")
server_config = Config.instance().settings.Server
Config.instance().listen_for_config_changes(self._update_config)
host = server_config.get("host", "localhost")
port = server_config.getint("port", 3080)
host = server_config.host
port = server_config.port
# clients will use the IP they use to connect to
# the controller if console_host is 0.0.0.0
@@ -83,13 +81,13 @@ class Controller:
self._load_controller_settings()
if server_config.getboolean("ssl"):
if server_config.ssl:
if sys.platform.startswith("win"):
log.critical("SSL mode is not supported on Windows")
raise SystemExit
self._ssl_context = self._create_ssl_context(server_config)
protocol = server_config.get("protocol", "http")
protocol = server_config.protocol
if self._ssl_context and protocol != "https":
log.warning("Protocol changed to 'https' for local compute because SSL is enabled".format(port))
protocol = "https"
@@ -100,8 +98,8 @@ class Controller:
host=host,
console_host=console_host,
port=port,
user=server_config.get("user", ""),
password=server_config.get("password", ""),
user=server_config.user,
password=server_config.password,
force=True,
connect=True,
ssl_context=self._ssl_context)
@@ -128,8 +126,8 @@ class Controller:
import ssl
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
certfile = server_config["certfile"]
certkey = server_config["certkey"]
certfile = server_config.certfile
certkey = server_config.certkey
try:
ssl_context.load_cert_chain(certfile, certkey)
except FileNotFoundError:
@@ -153,9 +151,8 @@ class Controller:
"""
if self._local_server:
server_config = Config.instance().get_section_config("Server")
self._local_server.user = server_config.get("user")
self._local_server.password = server_config.get("password")
self._local_server.user = Config.instance().settings.Server.user
self._local_server.password = Config.instance().settings.Server.password
async def stop(self):
@@ -169,7 +166,7 @@ class Controller:
except (ComputeError, ControllerError, OSError):
pass
await self.gns3vm.exit_vm()
#self.save()
self.save()
self._computes = {}
self._projects = {}
@@ -187,20 +184,6 @@ class Controller:
await self.load_projects()
def check_can_write_config(self):
"""
Check if the controller configuration can be written on disk
:returns: boolean
"""
try:
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
if not os.access(self._config_file, os.W_OK):
raise ControllerNotFoundError("Change rejected, cannot write to controller configuration file '{}'".format(self._config_file))
except OSError as e:
raise ControllerError("Change rejected: {}".format(e))
def save(self):
"""
Save the controller configuration on disk
@@ -209,68 +192,83 @@ class Controller:
if self._config_loaded is False:
return
controller_settings = {"gns3vm": self.gns3vm.__json__(),
"iou_license": self._iou_license_settings,
"appliances_etag": self._appliance_manager.appliances_etag,
"version": __version__}
if self._iou_license_settings["iourc_content"]:
# for compute in self._computes.values():
# if compute.id != "local" and compute.id != "vm":
# controller_settings["computes"].append({"host": compute.host,
# "name": compute.name,
# "port": compute.port,
# "protocol": compute.protocol,
# "user": compute.user,
# "password": compute.password,
# "compute_id": compute.id})
iou_config = Config.instance().settings.IOU
server_config = Config.instance().settings.Server
try:
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
with open(self._config_file, 'w+') as f:
json.dump(controller_settings, f, indent=4)
except OSError as e:
log.error("Cannot write controller configuration file '{}': {}".format(self._config_file, e))
if iou_config.iourc_path:
iourc_path = iou_config.iourc_path
else:
os.makedirs(os.path.dirname(server_config.secrets_dir), exist_ok=True)
iourc_path = os.path.join(server_config.secrets_dir, "gns3_iourc_license")
try:
with open(iourc_path, 'w+') as f:
f.write(self._iou_license_settings["iourc_content"])
log.info(f"iourc file '{iourc_path}' saved")
except OSError as e:
log.error(f"Cannot write IOU license file '{iourc_path}': {e}")
# if self._appliance_manager.appliances_etag:
# config._config.set("Controller", "appliances_etag", self._appliance_manager.appliances_etag)
# config.write_config()
def _load_controller_settings(self):
"""
Reload the controller configuration from disk
"""
try:
if not os.path.exists(self._config_file):
self._config_loaded = True
self.save()
with open(self._config_file) as f:
controller_settings = json.load(f)
except (OSError, ValueError) as e:
log.critical("Cannot load configuration file '{}': {}".format(self._config_file, e))
return []
# try:
# if not os.path.exists(self._config_file):
# self._config_loaded = True
# self.save()
# with open(self._config_file) as f:
# controller_settings = json.load(f)
# except (OSError, ValueError) as e:
# log.critical("Cannot load configuration file '{}': {}".format(self._config_file, e))
# return []
# load GNS3 VM settings
if "gns3vm" in controller_settings:
gns3_vm_settings = controller_settings["gns3vm"]
if "port" not in gns3_vm_settings:
# port setting was added in version 2.2.8
# the default port was 3080 before this
gns3_vm_settings["port"] = 3080
self.gns3vm.settings = gns3_vm_settings
# if "gns3vm" in controller_settings:
# gns3_vm_settings = controller_settings["gns3vm"]
# if "port" not in gns3_vm_settings:
# # port setting was added in version 2.2.8
# # the default port was 3080 before this
# gns3_vm_settings["port"] = 3080
# self.gns3vm.settings = gns3_vm_settings
# load the IOU license settings
if "iou_license" in controller_settings:
self._iou_license_settings = controller_settings["iou_license"]
iou_config = Config.instance().settings.IOU
server_config = Config.instance().settings.Server
self._appliance_manager.appliances_etag = controller_settings.get("appliances_etag")
self._appliance_manager.load_appliances()
#controller_config.getboolean("iou_license_check", True)
if iou_config.iourc_path:
iourc_path = iou_config.iourc_path
else:
iourc_path = os.path.join(server_config.secrets_dir, "gns3_iourc_license")
if os.path.exists(iourc_path):
try:
with open(iourc_path, 'r') as f:
self._iou_license_settings["iourc_content"] = f.read()
log.info(f"iourc file '{iourc_path}' loaded")
except OSError as e:
log.error(f"Cannot read IOU license file '{iourc_path}': {e}")
self._iou_license_settings["license_check"] = iou_config.license_check
#self._appliance_manager.appliances_etag = controller_config.get("appliances_etag", None)
#self._appliance_manager.load_appliances()
self._config_loaded = True
return controller_settings.get("computes", [])
async def load_projects(self):
"""
Preload the list of projects from disk
"""
server_config = Config.instance().get_section_config("Server")
projects_path = os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
server_config = Config.instance().settings.Server
projects_path = os.path.expanduser(server_config.projects_path)
os.makedirs(projects_path, exist_ok=True)
try:
for project_path in os.listdir(projects_path):
@@ -305,8 +303,8 @@ class Controller:
Get the image storage directory
"""
server_config = Config.instance().get_section_config("Server")
images_path = os.path.expanduser(server_config.get("images_path", "~/GNS3/images"))
server_config = Config.instance().settings.Server
images_path = os.path.expanduser(server_config.images_path)
os.makedirs(images_path, exist_ok=True)
return images_path
@@ -315,8 +313,8 @@ class Controller:
Get the configs storage directory
"""
server_config = Config.instance().get_section_config("Server")
configs_path = os.path.expanduser(server_config.get("configs_path", "~/GNS3/configs"))
server_config = Config.instance().settings.Server
configs_path = os.path.expanduser(server_config.configs_path)
os.makedirs(configs_path, exist_ok=True)
return configs_path
@@ -348,7 +346,7 @@ class Controller:
compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs)
self._computes[compute.id] = compute
self.save()
#self.save()
if connect:
asyncio.ensure_future(compute.connect())
self.notification.controller_emit("compute.created", compute.__json__())
@@ -394,7 +392,7 @@ class Controller:
await self.close_compute_projects(compute)
await compute.close()
del self._computes[compute_id]
self.save()
#self.save()
self.notification.controller_emit("compute.deleted", compute.__json__())
@property
@@ -557,8 +555,8 @@ class Controller:
def projects_directory(self):
server_config = Config.instance().get_section_config("Server")
return os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
server_config = Config.instance().settings.Server
return os.path.expanduser(server_config.projects_path)
@staticmethod
def instance():
+2 -2
View File
@@ -71,8 +71,8 @@ class ApplianceManager:
Get the image storage directory
"""
server_config = Config.instance().get_section_config("Server")
appliances_path = os.path.expanduser(server_config.get("appliances_path", "~/GNS3/appliances"))
server_config = Config.instance().settings.Server
appliances_path = os.path.expanduser(server_config.appliances_path)
os.makedirs(appliances_path, exist_ok=True)
return appliances_path
+2 -2
View File
@@ -121,9 +121,9 @@ class Compute:
else:
self._user = user.strip()
if password:
self._password = password.strip()
self._password = password
try:
self._auth = aiohttp.BasicAuth(self._user, self._password, "utf-8")
self._auth = aiohttp.BasicAuth(self._user, self._password.get_secret_value(), "utf-8")
except ValueError as e:
log.error(str(e))
else:
+2 -5
View File
@@ -413,9 +413,6 @@ class Project:
self._path = path
def _config(self):
return Config.instance().get_section_config("Server")
@property
def captures_directory(self):
"""
@@ -870,8 +867,8 @@ class Project:
depending of the operating system
"""
server_config = Config.instance().get_section_config("Server")
path = os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
server_config = Config.instance().settings.Server
path = os.path.expanduser(server_config.projects_path)
path = os.path.normpath(path)
try:
os.makedirs(path, exist_ok=True)
+3 -1
View File
@@ -112,7 +112,9 @@ class Symbols:
return symbols
def symbols_path(self):
directory = os.path.expanduser(Config.instance().get_section_config("Server").get("symbols_path", "~/GNS3/symbols"))
server_config = Config.instance().settings.Server
directory = os.path.expanduser(server_config.symbols_path)
if directory:
try:
os.makedirs(directory, exist_ok=True)
+1 -2
View File
@@ -142,8 +142,7 @@ class CrashReport:
log.warning(".git directory detected, crash reporting is turned off for developers.")
return
server_config = Config.instance().get_section_config("Server")
if server_config.getboolean("report_errors"):
if Config.instance().settings.Server.report_errors:
if not SENTRY_SDK_AVAILABLE:
log.warning("Cannot capture exception: Sentry SDK is not available")
+9 -1
View File
@@ -54,6 +54,10 @@ class ComputesRepository(BaseRepository):
async def create_compute(self, compute_create: schemas.ComputeCreate) -> models.Compute:
password = compute_create.password
if password:
password = password.get_secret_value()
db_compute = models.Compute(
compute_id=compute_create.compute_id,
name=compute_create.name,
@@ -61,7 +65,7 @@ class ComputesRepository(BaseRepository):
host=compute_create.host,
port=compute_create.port,
user=compute_create.user,
password=compute_create.password
password=password
)
self._db_session.add(db_compute)
await self._db_session.commit()
@@ -72,6 +76,10 @@ class ComputesRepository(BaseRepository):
update_values = compute_update.dict(exclude_unset=True)
password = compute_update.password
if password:
update_values["password"] = password.get_secret_value()
query = update(models.Compute) \
.where(models.Compute.compute_id == compute_id) \
.values(update_values)
+33 -55
View File
@@ -100,12 +100,10 @@ def parse_arguments(argv):
parser.add_argument("--config", help="Configuration file")
parser.add_argument("--certfile", help="SSL cert file")
parser.add_argument("--certkey", help="SSL key file")
parser.add_argument("--record", help="save curl requests into a file (for developers)")
parser.add_argument("-L", "--local", action="store_true", help="local mode (allows some insecure operations)")
parser.add_argument("-A", "--allow", action="store_true", help="allow remote connections to local console ports")
parser.add_argument("-q", "--quiet", action="store_true", help="do not show logs on stdout")
parser.add_argument("-d", "--debug", action="store_true", help="show debug logs")
parser.add_argument("--shell", action="store_true", help="start a shell inside the server (debugging purpose only you need to install ptpython before)")
parser.add_argument("--log", help="send output to logfile instead of console")
parser.add_argument("--logmaxsize", help="maximum logfile size in bytes (default is 10MB)")
parser.add_argument("--logbackupcount", help="number of historical log files to keep (default is 10)")
@@ -120,50 +118,37 @@ def parse_arguments(argv):
else:
Config.instance(profile=args.profile)
config = Config.instance().get_section_config("Server")
config = Config.instance().settings
defaults = {
"host": config.get("host", "0.0.0.0"),
"port": config.getint("port", 3080),
"ssl": config.getboolean("ssl", False),
"certfile": config.get("certfile", ""),
"certkey": config.get("certkey", ""),
"record": config.get("record", ""),
"local": config.getboolean("local", False),
"allow": config.getboolean("allow_remote_console", False),
"quiet": config.getboolean("quiet", False),
"debug": config.getboolean("debug", False),
"logfile": config.getboolean("logfile", ""),
"logmaxsize": config.getint("logmaxsize", 10000000), # default is 10MB
"logbackupcount": config.getint("logbackupcount", 10),
"logcompression": config.getboolean("logcompression", False)
"host": config.Server.host,
"port": config.Server.port,
"ssl": config.Server.ssl,
"certfile": config.Server.certfile,
"certkey": config.Server.certkey,
"local": config.Server.local,
"allow": config.Server.allow_remote_console,
"quiet": config.Server.quiet,
"debug": config.Server.debug,
"logfile": config.Server.logfile,
"logmaxsize": config.Server.logmaxsize,
"logbackupcount": config.Server.logbackupcount,
"logcompression": config.Server.logcompression
}
parser.set_defaults(**defaults)
return parser.parse_args(argv)
def set_config(args):
config = Config.instance()
server_config = config.get_section_config("Server")
jwt_secret_key = server_config.get("jwt_secret_key", None)
if not jwt_secret_key:
log.info("No JWT secret key configured, generating one...")
if not config._config.has_section("Server"):
config._config.add_section("Server")
config._config.set("Server", "jwt_secret_key", secrets.token_hex(32))
config.write_config()
server_config["local"] = str(args.local)
server_config["allow_remote_console"] = str(args.allow)
server_config["host"] = args.host
server_config["port"] = str(args.port)
server_config["ssl"] = str(args.ssl)
server_config["certfile"] = args.certfile
server_config["certkey"] = args.certkey
server_config["record"] = args.record
server_config["debug"] = str(args.debug)
server_config["shell"] = str(args.shell)
config.set_section_config("Server", server_config)
config = Config.instance().settings
config.Server.local = args.local
config.Server.allow_remote_console = args.allow
config.Server.host = args.host
config.Server.port = args.port
config.Server.ssl = args.ssl
config.Server.certfile = args.certfile
config.Server.certkey = args.certkey
config.Server.debug = args.debug
def pid_lock(path):
@@ -280,17 +265,13 @@ def run():
log.info("Config file {} loaded".format(config_file))
set_config(args)
server_config = Config.instance().get_section_config("Server")
config = Config.instance().settings
if server_config.getboolean("local"):
if config.Server.local:
log.warning("Local mode is enabled. Beware, clients will have full control on your filesystem")
if server_config.getboolean("auth"):
user = server_config.get("user", "").strip()
if not user:
log.critical("HTTP authentication is enabled but no username is configured")
return
log.info("HTTP authentication is enabled with username '{}'".format(user))
if config.Server.auth:
log.info("HTTP authentication is enabled with username '{}'".format(config.Server.user))
# we only support Python 3 version >= 3.6
if sys.version_info < (3, 6, 0):
@@ -311,8 +292,8 @@ def run():
return
CrashReport.instance()
host = server_config["host"]
port = int(server_config["port"])
host = config.Server.host
port = config.Server.port
PortManager.instance().console_host = host
signal_handling()
@@ -325,22 +306,19 @@ def run():
if log.getEffectiveLevel() == logging.DEBUG:
access_log = True
certfile = None
certkey = None
if server_config.getboolean("ssl"):
if config.Server.ssl:
if sys.platform.startswith("win"):
log.critical("SSL mode is not supported on Windows")
raise SystemExit
certfile = server_config["certfile"]
certkey = server_config["certkey"]
log.info("SSL is enabled")
config = uvicorn.Config(app,
host=host,
port=port,
access_log=access_log,
ssl_certfile=certfile,
ssl_keyfile=certkey)
ssl_certfile=config.Server.certfile,
ssl_keyfile=config.Server.certkey,
lifespan="on")
# overwrite uvicorn loggers with our own logger
for uvicorn_logger_name in ("uvicorn", "uvicorn.error"):
+1 -1
View File
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .config import ServerConfig
from .iou_license import IOULicense
from .links import Link
from .common import ErrorMessage
+3 -3
View File
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pydantic import BaseModel, Field, validator
from pydantic import BaseModel, Field, SecretStr, validator
from typing import List, Optional, Union
from uuid import UUID, uuid4
from enum import Enum
@@ -51,7 +51,7 @@ class ComputeCreate(ComputeBase):
"""
compute_id: Union[str, UUID] = Field(default_factory=uuid4)
password: Optional[str] = None
password: Optional[SecretStr] = None
class Config:
schema_extra = {
@@ -91,7 +91,7 @@ class ComputeUpdate(ComputeBase):
protocol: Optional[Protocol] = None
host: Optional[str] = None
port: Optional[int] = Field(None, gt=0, le=65535)
password: Optional[str] = None
password: Optional[SecretStr] = None
class Config:
schema_extra = {
+196
View File
@@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from pydantic import BaseModel, Field, SecretStr, validator
from typing import List
class ControllerSettings(BaseModel):
jwt_secret_key: str = None
jwt_algorithm: str = "HS256"
jwt_access_token_expire_minutes: int = 1440
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class VPCSSettings(BaseModel):
vpcs_path: str = "vpcs"
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class DynamipsSettings(BaseModel):
allocate_aux_console_ports: bool = False
mmap_support: bool = True
dynamips_path: str = "dynamips"
sparse_memory_support: bool = True
ghost_ios_support: bool = True
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class IOUSettings(BaseModel):
iourc_path: str = None
license_check: bool = True
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class QemuSettings(BaseModel):
enable_monitor: bool = True
monitor_host: str = "127.0.0.1"
enable_hardware_acceleration: bool = True
require_hardware_acceleration: bool = False
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class VirtualBoxSettings(BaseModel):
vboxmanage_path: str = None
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class VMwareSettings(BaseModel):
vmrun_path: str = None
vmnet_start_range: int = Field(2, ge=1, le=255)
vmnet_end_range: int = Field(255, ge=1, le=255) # should be limited to 19 on Windows
block_host_traffic: bool = False
@validator("vmnet_end_range")
def vmnet_port_range(cls, v, values):
if "vmnet_start_range" in values and v <= values["vmnet_start_range"]:
raise ValueError("vmnet_end_range must be > vmnet_start_range")
return v
class Config:
validate_assignment = True
anystr_strip_whitespace = True
class ServerProtocol(str, Enum):
http = "http"
https = "https"
class ServerSettings(BaseModel):
protocol: ServerProtocol = ServerProtocol.http
host: str = "0.0.0.0"
port: int = Field(3080, gt=0, le=65535)
secrets_dir: str = None
ssl: bool = False
certfile: str = None
certkey: str = None
images_path: str = "~/GNS3/images"
projects_path: str = "~/GNS3/projects"
appliances_path: str = "~/GNS3/appliances"
symbols_path: str = "~/GNS3/symbols"
configs_path: str = "~/GNS3/configs"
report_errors: bool = True
additional_images_paths: List[str] = Field(default_factory=list)
console_start_port_range: int = Field(5000, gt=0, le=65535)
console_end_port_range: int = Field(10000, gt=0, le=65535)
vnc_console_start_port_range: int = Field(5900, ge=5900, le=65535)
vnc_console_end_port_range: int = Field(10000, ge=5900, le=65535)
udp_start_port_range: int = Field(10000, gt=0, le=65535)
udp_end_port_range: int = Field(30000, gt=0, le=65535)
ubridge_path: str = "ubridge"
user: str = None
password: SecretStr = None
auth: bool = False
allowed_interfaces: List[str] = Field(default_factory=list)
default_nat_interface: str = None
logfile: str = None
logmaxsize: int = 10000000 # default is 10MB
logbackupcount: int = 10
logcompression: bool = False
local: bool = False
allow_remote_console: bool = False
quiet: bool = False
debug: bool = False
@validator("additional_images_paths", pre=True)
def split_additional_images_paths(cls, v):
if v:
return v.split(';')
return list()
@validator("allowed_interfaces", pre=True)
def split_allowed_interfaces(cls, v):
if v:
return v.split(',')
return list()
@validator("console_end_port_range")
def console_port_range(cls, v, values):
if "console_start_port_range" in values and v <= values["console_start_port_range"]:
raise ValueError("console_end_port_range must be > console_start_port_range")
return v
@validator("vnc_console_end_port_range")
def vnc_console_port_range(cls, v, values):
if "vnc_console_start_port_range" in values and v <= values["vnc_console_start_port_range"]:
raise ValueError("vnc_console_end_port_range must be > vnc_console_start_port_range")
return v
@validator("auth")
def validate_enable_auth(cls, v, values):
if v is True:
if "user" not in values or not values["user"]:
raise ValueError("HTTP authentication is enabled but no username is configured")
return v
class Config:
validate_assignment = True
anystr_strip_whitespace = True
use_enum_values = True
class ServerConfig(BaseModel):
Server: ServerSettings= ServerSettings()
Controller: ControllerSettings = ControllerSettings()
VPCS: VPCSSettings = VPCSSettings()
Dynamips: DynamipsSettings = DynamipsSettings()
IOU: IOUSettings = IOUSettings()
Qemu: QemuSettings = QemuSettings()
VirtualBox: VirtualBoxSettings = VirtualBoxSettings()
VMware: VMwareSettings = VMwareSettings()
+6 -20
View File
@@ -38,7 +38,7 @@ class AuthService:
def __init__(self):
self._server_config = Config.instance().get_section_config("Server")
self._controller_config = Config.instance().settings.Controller
def hash_password(self, password: str) -> str:
@@ -48,20 +48,6 @@ class AuthService:
return pwd_context.verify(password, hashed_password)
def get_secret_key(self):
"""
Should only be used by tests.
"""
return self._server_config.get("jwt_secret_key", None)
def get_algorithm(self):
"""
Should only be used by tests.
"""
return self._server_config.get("jwt_algorithm", None)
def create_access_token(
self,
username,
@@ -70,15 +56,15 @@ class AuthService:
) -> str:
if not expires_in:
expires_in = self._server_config.getint("jwt_access_token_expire_minutes", 1440)
expires_in = self._controller_config.jwt_access_token_expire_minutes
expire = datetime.utcnow() + timedelta(minutes=expires_in)
to_encode = {"sub": username, "exp": expire}
if secret_key is None:
secret_key = self._server_config.get("jwt_secret_key", None)
secret_key = self._controller_config.jwt_secret_key
if secret_key is None:
secret_key = DEFAULT_JWT_SECRET_KEY
log.error("A JWT secret key must be configured to secure the server, using an unsecured default key!")
algorithm = self._server_config.get("jwt_algorithm", "HS256")
algorithm = self._controller_config.jwt_algorithm
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm)
return encoded_jwt
@@ -91,11 +77,11 @@ class AuthService:
)
try:
if secret_key is None:
secret_key = self._server_config.get("jwt_secret_key", None)
secret_key = self._controller_config.jwt_secret_key
if secret_key is None:
secret_key = DEFAULT_JWT_SECRET_KEY
log.error("A JWT secret key must be configured to secure the server, using an unsecured default key!")
algorithm = self._server_config.get("jwt_algorithm", "HS256")
algorithm = self._controller_config.jwt_algorithm
payload = jwt.decode(token, secret_key, algorithms=[algorithm])
username: str = payload.get("sub")
if username is None:
+7 -7
View File
@@ -35,8 +35,8 @@ def list_images(type):
files = set()
images = []
server_config = Config.instance().get_section_config("Server")
general_images_directory = os.path.expanduser(server_config.get("images_path", "~/GNS3/images"))
server_config = Config.instance().settings.Server
general_images_directory = os.path.expanduser(server_config.images_path)
# Subfolder of the general_images_directory specific to this VM type
default_directory = default_images_directory(type)
@@ -106,8 +106,8 @@ def default_images_directory(type):
"""
:returns: Return the default directory for a node type
"""
server_config = Config.instance().get_section_config("Server")
img_dir = os.path.expanduser(server_config.get("images_path", "~/GNS3/images"))
server_config = Config.instance().settings.Server
img_dir = os.path.expanduser(server_config.images_path)
if type == "qemu":
return os.path.join(img_dir, "QEMU")
elif type == "iou":
@@ -125,17 +125,17 @@ def images_directories(type):
:param type: Type of emulator
"""
server_config = Config.instance().get_section_config("Server")
server_config = Config.instance().settings.Server
paths = []
img_dir = os.path.expanduser(server_config.get("images_path", "~/GNS3/images"))
img_dir = os.path.expanduser(server_config.images_path)
type_img_directory = default_images_directory(type)
try:
os.makedirs(type_img_directory, exist_ok=True)
paths.append(type_img_directory)
except (OSError, PermissionError):
pass
for directory in server_config.get("additional_images_path", "").split(";"):
for directory in server_config.additional_images_paths:
paths.append(directory)
# Compatibility with old topologies we look in parent directory
paths.append(img_dir)
+1 -3
View File
@@ -184,9 +184,7 @@ def interfaces():
results = []
if not sys.platform.startswith("win"):
allowed_interfaces = Config.instance().get_section_config("Server").get("allowed_interfaces", None)
if allowed_interfaces:
allowed_interfaces = allowed_interfaces.split(',')
allowed_interfaces = Config.instance().settings.Server.allowed_interfaces
net_if_addrs = psutil.net_if_addrs()
for interface in sorted(net_if_addrs.keys()):
if allowed_interfaces and interface not in allowed_interfaces and not interface.startswith("gns3tap"):
+3 -5
View File
@@ -27,8 +27,8 @@ def get_default_project_directory():
depending of the operating system
"""
server_config = Config.instance().get_section_config("Server")
path = os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
server_config = Config.instance().settings.Server
path = os.path.expanduser(server_config.projects_path)
path = os.path.normpath(path)
try:
os.makedirs(path, exist_ok=True)
@@ -45,11 +45,9 @@ def check_path_allowed(path):
Raise a 403 in case of error
"""
config = Config.instance().get_section_config("Server")
project_directory = get_default_project_directory()
if len(os.path.commonprefix([project_directory, path])) == len(project_directory):
return
if "local" in config and config.getboolean("local") is False:
if Config.instance().settings.Server.local is False:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="The path is not allowed")