-
Notifications
You must be signed in to change notification settings - Fork 379
Short setups to provide X display to container
mviereck edited this page Apr 29, 2021
·
19 revisions
This is similar to x11docker option --hostdisplay
:
- Share access to host X server with environment variable
DISPLAY
and X unix socket in/tmp/.X11-unix
. - Allow access with
xhost
for current local user and create a similar container user.- Instead of using
xhost
you can set up a cookie.
- Instead of using
- Allow shared memory with
--ipc=host
to avoid RAM access failures and rendering glitches due to X extensionMIT-SHM
.- If you configure your host X server to run without extension
MIT-SHM
, you can drop--ipc=host
. - An alternate solution based on
LD_PRELOAD
setsXShmQueryExtension
to false.
- If you configure your host X server to run without extension
- Create container user similar to host user to avoid root in container with
--user $(id -u):$(id -g)
. - Drop all capabilities with
--cap-drop=ALL --security-opt=no-new-privileges
to improve container security. - Warning: This nice short solution has the disadvantage of breaking container isolation. X security leaks like keylogging and remote host control can be abused by container applications.
xhost +SI:localuser:$(id -un)
docker run --rm \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
--ipc=host \
--user $(id -u):$(id -g) \
--cap-drop=ALL \
--security-opt=no-new-privileges \
IMAGENAME IMAGECOMMAND
This is similar to x11docker option --xephyr
and avoids the above security leaks:
- Run Xephyr with disabled shared memory (X extension
MIT-SHM
) and disabled X extensionXTEST
. - Set
DISPLAY
and share access to new unix socket/tmp/.X11-unix/X1
. - Create container user similar to host user to avoid root in container with
--user $(id -u):$(id -g)
. - Drop all capabilities with
--cap-drop=ALL --security-opt=no-new-privileges
to improve container security.
Displaynumber=1
Xephyr :$Displaynumber -extension MIT-SHM -extension XTEST &
docker run --rm \
-e DISPLAY=:$Displaynumber \
-v /tmp/.X11-unix/X$Displaynumber:/tmp/.X11-unix/X$Displaynumber:rw \
--user $(id -u):$(id -g) \
--cap-drop=ALL \
--security-opt=no-new-privileges \
IMAGENAME IMAGECOMMAND
This solution is more secure than the above one as it does not give access to display :0 with host applications and does not need --ipc=host
. To use this with single applications you can run a host window manager on Xephyr display, too, for example with env DISPLAY=:1 x-window-manager
.
- Finds free display number and creates an X cookie.
- Unprivileged container user with entry in
/etc/passwd
of container. - Restricted container capabilities to improve security.
- A window manager from host can be specified.
- Examples:
xephyrdocker : x11docker/xfce
xephyrdocker "openbox --sm-disable" x11docker/lxde pcmanfm
xephyrdocker xfwm4 --device /dev/snd jess/nes /games/zelda.rom
#! /bin/bash
#
# Xephyrdocker: Example script to run docker GUI applications in Xephyr.
#
# Usage:
# Xephyrdocker WINDOWMANAGER DOCKERIMAGE [IMAGECOMMAND [ARGS]]
#
# WINDOWMANAGER host window manager for use with single GUI applications.
# To run without window manager from host, use ":"
# DOCKERIMAGE docker image containing GUI applications or a desktop
# IMAGECOMMAND command to run in image
#
Windowmanager="$1" && shift
Dockerimage="$*"
# Container user
Useruid=$(id -u)
Usergid=$(id -g)
Username="$(id -un)"
[ "$Useruid" = "0" ] && Useruid=1000 && Usergid=1000 && Username="user$Useruid"
# Find free display number
for ((Newdisplaynumber=1 ; Newdisplaynumber <= 100 ; Newdisplaynumber++)) ; do
[ -e /tmp/.X11-unix/X$Newdisplaynumber ] || break
done
Newxsocket=/tmp/.X11-unix/X$Newdisplaynumber
# Cache folder and files
Cachefolder=/tmp/Xephyrdocker_X$Newdisplaynumber
[ -e "$Cachefolder" ] && rm -R "$Cachefolder"
mkdir -p $Cachefolder
Xclientcookie=$Cachefolder/Xcookie.client
Xservercookie=$Cachefolder/Xcookie.server
Xinitrc=$Cachefolder/xinitrc
Etcpasswd=$Cachefolder/passwd
# Command to run docker
# --rm Created container will be discarded.
# -e DISPLAY=$Newdisplay Set environment variable to new display.
# -e XAUTHORITY=/Xcookie Set environment variable XAUTHORITY to provided cookie.
# -v $Xclientcookie:/Xcookie:ro Provide cookie file to container.
# -v $NewXsocket:$NewXsocket:ro Share new X socket of Xephyr.
# --user $Useruid:$Usergid Security: avoid root in container.
# -v $Etcpasswd:/etc/passwd:ro Share custom /etc/passwd file with user entry.
# --group-add audio Allow ALSA access to /dev/snd if shared with '--device /dev/snd' .
# --cap-drop ALL Security: disable needless capabilities.
# --security-opt no-new-privileges Security: forbid new privileges.
# --init Run with init system tini if available.
Dockercommand="docker run --rm \
-e DISPLAY=:$Newdisplaynumber \
-e XAUTHORITY=/Xcookie \
-v $Xclientcookie:/Xcookie:ro \
-v $Newxsocket:$Newxsocket:rw \
--user $Useruid:$Usergid \
-v $Etcpasswd:/etc/passwd:ro \
--group-add audio \
--env HOME=/tmp \
--cap-drop ALL \
--security-opt no-new-privileges \
$(command -v docker-init >/dev/null && echo --init) \
$Dockerimage"
echo "docker command:
$Dockercommand
"
# Command to run Xorg or Xephyr
# /usr/bin/Xephyr An absolute path to X server executable must be given for xinit.
# :$Newdisplaynumber First argument has to be new display.
# -auth $Xservercookie Path to cookie file for X server.
# -extension MIT-SHM Disable MIT-SHM to avoid rendering glitches and bad RAM access.
# -nolisten tcp Disable TCP connections, local access only.
# -retro Nice retro look.
Xcommand="/usr/bin/Xephyr :$Newdisplaynumber \
-auth $Xservercookie \
-extension MIT-SHM \
-nolisten tcp \
-screen 1000x750x24 \
-retro"
echo "X server command:
$Xcommand
"
# Create /etc/passwd with unprivileged user
echo "root:x:0:0:root:/root:/bin/sh" >$Etcpasswd
echo "$Username:x:$Useruid:$Usergid:$Username,,,:/tmp:/bin/sh" >> $Etcpasswd
# Create xinitrc
{ echo "#! /bin/bash"
echo "# set environment variables to new display and new cookie"
echo "export DISPLAY=:$Newdisplaynumber"
echo "export XAUTHORITY=$Xclientcookie"
echo "# same keyboard layout as on host"
echo "echo '$(setxkbmap -display $DISPLAY -print)' | xkbcomp - :$Newdisplaynumber"
echo "# create new XAUTHORITY cookie file"
echo ":> $Xclientcookie"
echo "xauth add :$Newdisplaynumber . $(mcookie)"
echo "# create prepared cookie with localhost identification disabled by ffff,"
echo "# needed if X socket is shared instead connecting over tcp. ffff means 'familiy wild'"
echo 'Cookie=$(xauth nlist '":$Newdisplaynumber | sed -e 's/^..../ffff/')"
echo 'echo $Cookie | xauth -f '$Xclientcookie' nmerge -'
echo "cp $Xclientcookie $Xservercookie"
echo "chmod 644 $Xclientcookie"
echo "# run window manager in Xephyr"
echo $Windowmanager' & Windowmanagerpid=$!'
echo "# show docker log"
echo 'tail --retry -n +1 -F '$Dockerlogfile' 2>/dev/null & Tailpid=$!'
echo "# run docker"
echo "$Dockercommand"
} > $Xinitrc
xinit $Xinitrc -- $Xcommand
rm -Rf $Cachefolder
x11docker uses VcXsrv to provide an X display. Some day I'll write a short how-to on how to connect to VcXsrv with X over IP without x11docker.
A blogpost about providing XQuartz to docker containers. I did not test it, just providing this link as a pointer to a possible solution.