From 6ba03a78a975e476d61f3e427d69a4b67c8371c7 Mon Sep 17 00:00:00 2001 From: Xenorf <90411648+Xenorf@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:46:20 +0000 Subject: [PATCH 1/4] Adding support for x11 forwarding --- exegol/model/ExegolContainer.py | 55 ++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/exegol/model/ExegolContainer.py b/exegol/model/ExegolContainer.py index e49f4800..2f646059 100644 --- a/exegol/model/ExegolContainer.py +++ b/exegol/model/ExegolContainer.py @@ -1,4 +1,6 @@ import os +import subprocess +import random import shutil from datetime import datetime from typing import Optional, Dict, Sequence, Tuple, Union @@ -16,6 +18,7 @@ from exegol.utils.ContainerLogStream import ContainerLogStream from exegol.utils.ExeLog import logger, console from exegol.utils.imgsync.ImageScriptSync import ImageScriptSync +from exegol.utils.GuiUtils import GuiUtils class ExegolContainer(ExegolContainerTemplate, SelectableInterface): @@ -156,7 +159,7 @@ def spawnShell(self): logger.info(f"Shared host device: {device.split(':')[0]}") logger.success(f"Opening shell in Exegol '{self.name}'") # In case of multi-user environment, xhost must be set before opening each session to be sure - self.__applyXhostACL() + self.__applyX11ACLs() # Using system command to attach the shell to the user terminal (stdin / stdout / stderr) envs = self.config.getShellEnvs() options = "" @@ -281,7 +284,7 @@ def __preStartSetup(self): Operation to be performed before starting a container :return: """ - self.__applyXhostACL() + self.__applyX11ACLs() def __check_start_version(self): """ @@ -305,7 +308,7 @@ def postCreateSetup(self, is_temporary: bool = False): Operation to be performed after creating a container :return: """ - self.__applyXhostACL() + self.__applyX11ACLs() # if not a temporary container, apply custom config if not is_temporary: # Update entrypoint script in the container @@ -318,10 +321,11 @@ def postCreateSetup(self, is_temporary: bool = False): if "is not running" in e.explanation: logger.critical("An unexpected error occurred. Exegol cannot start the container after its creation...") - def __applyXhostACL(self): + def __applyX11ACLs(self): """ If X11 (GUI) is enabled, allow X11 access on host ACL (if not already allowed) for linux and mac. - On Windows host, WSLg X11 don't have xhost ACL. + If the host is accessed by SSH, propagate xauth cookie authentication if applicable. + On Windows host, WSLg X11 don't have xhost ACL. #TODO xauth remote x11 forwarding :return: """ if self.config.isGUIEnable() and not self.__xhost_applied and not EnvInfo.isWindowsHost(): @@ -334,16 +338,39 @@ def __applyXhostACL(self): logger.error(f"The [green]xhost[/green] command is not available on your [bold]host[/bold]. " f"Exegol was unable to allow your container to access your graphical environment ({debug_msg}).") return - - if EnvInfo.isMacHost(): - logger.debug(f"Adding xhost ACL to localhost") - # add xquartz inet ACL - with console.status(f"Starting XQuartz...", spinner_style="blue"): - os.system(f"xhost + localhost > /dev/null") + + # Another way of passing the argument without importing subprocess: os.system('xauth list | grep "${DISPLAY%.*}" > '+f"{self.config.getPrivateVolumePath()}/testyu") + logger.debug(f"DISPLAY variable: {GuiUtils.getDisplayEnv()}") + tmpXauthority = f"/tmp/.tmpXauthority{random.randint(0, 999)}" + output = os.system(f"xauth extract {tmpXauthority} $DISPLAY > /dev/null 2>&1") + xauthEntry = subprocess.check_output(f"xauth -f {tmpXauthority} list 2>/dev/null",shell=True).decode() + logger.debug(f"xauthEntry to propagate: {xauthEntry}") + display_host = GuiUtils.getDisplayEnv().split(':')[0] + if display_host=='': + logger.debug("Connecting to container from local GUI, no X11 forwarding to set up") + # TODO verify that the display format is the same on macOS, otherwise might not set up xauth and xhost correctly + if EnvInfo.isMacHost(): + logger.debug(f"Adding xhost ACL to localhost") + # add xquartz inet ACL + with console.status(f"Starting XQuartz...", spinner_style="blue"): + os.system(f"xhost + localhost > /dev/null") + else: + logger.debug(f"Adding xhost ACL to local:{self.config.getUsername()}") + # add linux local ACL + os.system(f"xhost +local:{self.config.getUsername()} > /dev/null") + elif display_host=="localhost" and self.config.getTextNetworkMode() == "bridge": + logger.warning("X11 forwarding won't work on a bridged container unless you specify \"X11UseLocalhost no\" in your host sshd_config") + elif display_host=="localhost": + logger.debug("X11UseLocalhost directive is set to \"yes\" or unspecified, X11 connection can be received only on loopback"); + # Modifing the entry to convert /unix: to localhost: + xauthEntry = f"localhost:{xauthEntry.split(':')[1]}" else: - logger.debug(f"Adding xhost ACL to local:{self.config.getUsername()}") - # add linux local ACL - os.system(f"xhost +local:{self.config.getUsername()} > /dev/null") + logger.debug("X11UseLocalhost directive is set to \"no\", X11 connection can be received from anywere"); + if xauthEntry: + logger.debug(f"Adding xauth cookie to container: {xauthEntry}") + self.exec(f"xauth add {xauthEntry}", as_daemon=False, quiet=True) + logger.debug(f"Removing {tmpXauthority}") + os.remove(tmpXauthority) def __updatePasswd(self): """ From 9cd86aac7716cc28eedc5ae2c7560cdad507a683 Mon Sep 17 00:00:00 2001 From: Xenorf <90411648+Xenorf@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:26:55 +0000 Subject: [PATCH 2/4] improved messages and initialisation --- exegol/model/ExegolContainer.py | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/exegol/model/ExegolContainer.py b/exegol/model/ExegolContainer.py index 2f646059..2264fbd7 100644 --- a/exegol/model/ExegolContainer.py +++ b/exegol/model/ExegolContainer.py @@ -339,12 +339,8 @@ def __applyX11ACLs(self): f"Exegol was unable to allow your container to access your graphical environment ({debug_msg}).") return - # Another way of passing the argument without importing subprocess: os.system('xauth list | grep "${DISPLAY%.*}" > '+f"{self.config.getPrivateVolumePath()}/testyu") logger.debug(f"DISPLAY variable: {GuiUtils.getDisplayEnv()}") - tmpXauthority = f"/tmp/.tmpXauthority{random.randint(0, 999)}" - output = os.system(f"xauth extract {tmpXauthority} $DISPLAY > /dev/null 2>&1") - xauthEntry = subprocess.check_output(f"xauth -f {tmpXauthority} list 2>/dev/null",shell=True).decode() - logger.debug(f"xauthEntry to propagate: {xauthEntry}") + display_host = GuiUtils.getDisplayEnv().split(':')[0] if display_host=='': logger.debug("Connecting to container from local GUI, no X11 forwarding to set up") @@ -358,19 +354,43 @@ def __applyX11ACLs(self): logger.debug(f"Adding xhost ACL to local:{self.config.getUsername()}") # add linux local ACL os.system(f"xhost +local:{self.config.getUsername()} > /dev/null") - elif display_host=="localhost" and self.config.getTextNetworkMode() == "bridge": + return + + if shutil.which("xauth") is None: + if EnvInfo.is_linux_shell: + debug_msg = "Try to install the package [green]xorg-xauth[/green] or maybe you don't have X11 on your host?" + else: + debug_msg = "or you don't have one" + logger.error(f"The [green]xauth[/green] command is not available on your [bold]host[/bold]. " + f"Exegol was unable to allow your container to access your graphical environment ({debug_msg}).") + return + + if display_host=="localhost" and self.config.getTextNetworkMode() != "host": logger.warning("X11 forwarding won't work on a bridged container unless you specify \"X11UseLocalhost no\" in your host sshd_config") - elif display_host=="localhost": + logger.warning("[red]Be aware[/red] changing \"X11UseLocalhost\" value can [red]expose your device[/red], correct firewalling is [red]required[/red]") + logger.warning("The following documentation can be usefull to limit the exposure of your x11 socket: https://studioware.com/wikislax/index.php?title=X11_over_the_network#X11_firewalling") + return + + # Setting up the temporary file to pass the xauth cookie to the container + tmpXauthority = f"/tmp/.tmpXauthority{random.randint(0, 999)}" + logger.debug(f"Extracting xauth entries to {tmpXauthority}") + os.system(f"xauth extract {tmpXauthority} $DISPLAY > /dev/null 2>&1") + xauthEntry = subprocess.check_output(f"xauth -f {tmpXauthority} list 2>/dev/null",shell=True).decode() + logger.debug(f"xauthEntry to propagate: {xauthEntry}") + if display_host=="localhost": logger.debug("X11UseLocalhost directive is set to \"yes\" or unspecified, X11 connection can be received only on loopback"); # Modifing the entry to convert /unix: to localhost: xauthEntry = f"localhost:{xauthEntry.split(':')[1]}" else: logger.debug("X11UseLocalhost directive is set to \"no\", X11 connection can be received from anywere"); + # Check if the host has a xauth entry corresponding to the current display. if xauthEntry: logger.debug(f"Adding xauth cookie to container: {xauthEntry}") self.exec(f"xauth add {xauthEntry}", as_daemon=False, quiet=True) logger.debug(f"Removing {tmpXauthority}") os.remove(tmpXauthority) + else: + logger.warning(f"No xauth cookie corresponding to the current display was found.") def __updatePasswd(self): """ From 33866efb8a27bb8fdaa907812deb6b6b72515c02 Mon Sep 17 00:00:00 2001 From: Xenorf <90411648+Xenorf@users.noreply.github.com> Date: Sun, 1 Sep 2024 09:41:17 +0000 Subject: [PATCH 3/4] better tmp file and logs + comments --- exegol/model/ExegolContainer.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/exegol/model/ExegolContainer.py b/exegol/model/ExegolContainer.py index 2264fbd7..45cdb617 100644 --- a/exegol/model/ExegolContainer.py +++ b/exegol/model/ExegolContainer.py @@ -1,6 +1,6 @@ import os import subprocess -import random +import tempfile import shutil from datetime import datetime from typing import Optional, Dict, Sequence, Tuple, Union @@ -328,6 +328,7 @@ def __applyX11ACLs(self): On Windows host, WSLg X11 don't have xhost ACL. #TODO xauth remote x11 forwarding :return: """ + # TODO check if the xauth propagation should be performed on Windows, if so remove the "and not EnvInfo.isWindowsHost()" if self.config.isGUIEnable() and not self.__xhost_applied and not EnvInfo.isWindowsHost(): self.__xhost_applied = True # Can be applied only once per execution if shutil.which("xhost") is None: @@ -340,17 +341,18 @@ def __applyX11ACLs(self): return logger.debug(f"DISPLAY variable: {GuiUtils.getDisplayEnv()}") - + # Extracts the left part of the display variable to determine if remote access is used display_host = GuiUtils.getDisplayEnv().split(':')[0] + # Left part is empty, local access is used to start Exegol if display_host=='': logger.debug("Connecting to container from local GUI, no X11 forwarding to set up") - # TODO verify that the display format is the same on macOS, otherwise might not set up xauth and xhost correctly + # TODO verify that the display format is the same on macOS, otherwise might not set up xauth and xhost correctly if EnvInfo.isMacHost(): logger.debug(f"Adding xhost ACL to localhost") # add xquartz inet ACL with console.status(f"Starting XQuartz...", spinner_style="blue"): os.system(f"xhost + localhost > /dev/null") - else: + elif not EnvInfo.isWindowsHost(): logger.debug(f"Adding xhost ACL to local:{self.config.getUsername()}") # add linux local ACL os.system(f"xhost +local:{self.config.getUsername()} > /dev/null") @@ -358,31 +360,37 @@ def __applyX11ACLs(self): if shutil.which("xauth") is None: if EnvInfo.is_linux_shell: - debug_msg = "Try to install the package [green]xorg-xauth[/green] or maybe you don't have X11 on your host?" + debug_msg = "Try to install the package [green]xorg-xauth[/green] to support X11 forwarding in your current environment?" else: - debug_msg = "or you don't have one" + debug_msg = "or it might not be supported for now" logger.error(f"The [green]xauth[/green] command is not available on your [bold]host[/bold]. " f"Exegol was unable to allow your container to access your graphical environment ({debug_msg}).") return - if display_host=="localhost" and self.config.getTextNetworkMode() != "host": + # If the left part of the display variable is "localhost", x11 socket is exposed only on loopback and remote access is used + # If the container is not in host mode, it won't be able to reach the loopback interface of the host + if display_host=="localhost" and self.config.getNetworkMode() != "host": logger.warning("X11 forwarding won't work on a bridged container unless you specify \"X11UseLocalhost no\" in your host sshd_config") logger.warning("[red]Be aware[/red] changing \"X11UseLocalhost\" value can [red]expose your device[/red], correct firewalling is [red]required[/red]") - logger.warning("The following documentation can be usefull to limit the exposure of your x11 socket: https://studioware.com/wikislax/index.php?title=X11_over_the_network#X11_firewalling") + # TODO Add documentation to restrict the exposure of the x11 socket to the docker subnet return - # Setting up the temporary file to pass the xauth cookie to the container - tmpXauthority = f"/tmp/.tmpXauthority{random.randint(0, 999)}" + # Extracting the xauth cookie corresponding to the current display to a temporary file and reading it from there (grep cannot be used because display names are not accurate enough) + _, tmpXauthority = tempfile.mkstemp() logger.debug(f"Extracting xauth entries to {tmpXauthority}") os.system(f"xauth extract {tmpXauthority} $DISPLAY > /dev/null 2>&1") xauthEntry = subprocess.check_output(f"xauth -f {tmpXauthority} list 2>/dev/null",shell=True).decode() logger.debug(f"xauthEntry to propagate: {xauthEntry}") + + # Replacing the hostname with localhost to support loopback exposed x11 socket and container in host mode (loopback is the same) if display_host=="localhost": - logger.debug("X11UseLocalhost directive is set to \"yes\" or unspecified, X11 connection can be received only on loopback"); + logger.debug("X11UseLocalhost directive is set to \"yes\" or unspecified, X11 connections can be received only on loopback"); # Modifing the entry to convert /unix: to localhost: xauthEntry = f"localhost:{xauthEntry.split(':')[1]}" else: - logger.debug("X11UseLocalhost directive is set to \"no\", X11 connection can be received from anywere"); + # TODO latter implement a check to see if the x11 socket is correctly firewalled and warn the user if it is not + logger.debug("X11UseLocalhost directive is set to \"no\", X11 connections can be received from anywere"); + # Check if the host has a xauth entry corresponding to the current display. if xauthEntry: logger.debug(f"Adding xauth cookie to container: {xauthEntry}") From eaab349ed2b2f62d8ca34a01b9591e04dc3c62b4 Mon Sep 17 00:00:00 2001 From: Xenorf <90411648+Xenorf@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:21:32 +0000 Subject: [PATCH 4/4] x11 forwarding not supported macOS --- exegol/model/ExegolContainer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/model/ExegolContainer.py b/exegol/model/ExegolContainer.py index 45cdb617..69c60f71 100644 --- a/exegol/model/ExegolContainer.py +++ b/exegol/model/ExegolContainer.py @@ -344,7 +344,7 @@ def __applyX11ACLs(self): # Extracts the left part of the display variable to determine if remote access is used display_host = GuiUtils.getDisplayEnv().split(':')[0] # Left part is empty, local access is used to start Exegol - if display_host=='': + if display_host=='' or EnvInfo.isMacHost(): logger.debug("Connecting to container from local GUI, no X11 forwarding to set up") # TODO verify that the display format is the same on macOS, otherwise might not set up xauth and xhost correctly if EnvInfo.isMacHost():