x11 - È possibile eseguire applicazioni GUI in un contenitore Docker?


10 Answers

Xauthority diventa un problema con i nuovi sistemi. Posso scartare qualsiasi protezione con xhost + prima di eseguire i miei contenitori docker, oppure posso passare un file Xauthority ben preparato. I file Xauthority tipici sono specifici per hostname. Con la finestra mobile, ogni contenitore può avere un nome host diverso (impostato con docker run -h), ma nemmeno impostare il nome host del contenitore identico al sistema host non è stato di aiuto nel mio caso. xeyes (mi piace questo esempio) semplicemente ignorerebbe il magic cookie e non passerebbe alcuna credenziale al server. Quindi riceviamo un messaggio di errore 'Nessun protocollo specificato Impossibile aprire la visualizzazione'

Il file Xauthority può essere scritto in un modo tale che l'hostname non abbia importanza. Abbiamo bisogno di impostare la famiglia di autenticazione su "FamilyWild". Non sono sicuro, se xauth ha una riga di comando adeguata per questo, ecco un esempio che combina xauth e sed per farlo. Abbiamo bisogno di cambiare i primi 16 bit dell'output nlist. Il valore di FamilyWild è 65535 o 0xffff.

docker build -t xeyes - << __EOF__
FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps
ENV DISPLAY :0
CMD xeyes
__EOF__
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
xauth nlist :0 | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
docker run -ti -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH xeyes
Question

Come puoi eseguire le applicazioni GUI in un contenitore Docker ?

Ci sono delle immagini che configurano vncserver o qualcosa che puoi, ad esempio, aggiungere una sandbox speedbump in più per dire Firefox?




Con i volumi di dati del docker è molto facile esporre il socket del dominio unix di xorg all'interno del contenitore.

Ad esempio, con un Dockerfile come questo:

FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps
ENV DISPLAY :0
CMD xeyes

Potresti fare quanto segue:

$ docker build -t xeyes - < Dockerfile
$ XSOCK=/tmp/.X11-unix/X0
$ docker run -v $XSOCK:$XSOCK xeyes

Questo ovviamente è lo stesso di X-forwarding. Garantisce al contenitore l'accesso completo a xserver sull'host, quindi è consigliato solo se si ha fiducia in ciò che si trova all'interno.

Nota: se si è preoccupati per la sicurezza, una soluzione migliore sarebbe limitare l'app con controllo di accesso mandatory- o role-based- . Docker raggiunge un isolamento piuttosto buono, ma è stato progettato con uno scopo diverso in mente. Usa AppArmor , SELinux o GrSecurity , che sono stati progettati per risolvere il tuo problema.




OSX

Jürgen Weigert ha la migliore risposta che ha funzionato per me su Ubuntu, tuttavia su OSX, la docker viene eseguita all'interno di VirtualBox e quindi la soluzione non funziona senza ulteriore lavoro.

Ho lavorato con questi ingredienti aggiuntivi:

  1. Xquartz (OSX non viene più fornito con il server X11)
  2. inoltro socket con socat (brew install socat)
  3. script bash per avviare il contenitore

Apprezzerei i commenti degli utenti per migliorare questa risposta per OSX, non sono sicuro che il socket forwarding per X sia sicuro, ma il mio scopo è quello di eseguire localmente il container docker.

Inoltre, lo script è un po 'fragile in quanto non è facile ottenere l'indirizzo IP della macchina poiché è sul nostro wireless locale, quindi è sempre un IP casuale.

Lo script BASH che utilizzo per avviare il contenitore:

#!/usr/bin/env bash

CONTAINER=py3:2016-03-23-rc3
COMMAND=/bin/bash
NIC=en0

# Grab the ip address of this box
IPADDR=$(ifconfig $NIC | grep "inet " | awk '{print $2}')

DISP_NUM=$(jot -r 1 100 200)  # random display number between 100 and 200

PORT_NUM=$((6000 + DISP_NUM)) # so multiple instances of the container won't interfer with eachother

socat TCP-LISTEN:${PORT_NUM},reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\" 2>&1 > /dev/null &

XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth.$USER.$$
touch $XAUTH
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -

docker run \
    -it \
    --rm \
    --user=$USER \
    --workdir="/Users/$USER" \
    -v "/Users/$USER:/home/$USER:rw" \
    -v $XSOCK:$XSOCK:rw \
    -v $XAUTH:$XAUTH:rw \
    -e DISPLAY=$IPADDR:$DISP_NUM \
    -e XAUTHORITY=$XAUTH \
    $CONTAINER \
    $COMMAND

rm -f $XAUTH
kill %1       # kill the socat job launched above

Sono in grado di ottenere xeyes e matplotlib lavorando con questo approccio.

Windows 7+

È un po 'più facile su Windows 7+ con MobaXterm:

  1. Installa MobaXterm per Windows
  2. Avvia MobaXterm
  3. Configura il server X: Impostazioni -> X11 (scheda) -> imposta l'accesso remoto X11 a pieno
  4. Utilizzare questo script BASH per avviare il contenitore

run_docker.bash :

#!/usr/bin/env bash

CONTAINER=py3:2016-03-23-rc3
COMMAND=/bin/bash
DISPLAY="$(hostname):0"
USER=$(whoami)

docker run \
    -it \
    --rm \
    --user=$USER \
    --workdir="/home/$USER" \
    -v "/c/Users/$USER:/home/$USER:rw" \
    -e DISPLAY \
    $CONTAINER \
    $COMMAND




Condivisione visualizzazione host: 0, come indicato in altre risposte, presenta due inconvenienti:

  • Rompe l'isolamento del contenitore a causa di alcune perdite di sicurezza X. Ad esempio, keylogging con xev o xinput è possibile e il controllo remoto delle applicazioni host con xdotool .
  • Le applicazioni possono presentare errori di rendering e errori di accesso alla RAM dovuti alla mancanza di memoria condivisa per l'estensione X MIT-SHM. (Può anche essere risolto con l'opzione di isolamento degradante --ipc=host ).

Di seguito uno script di esempio per eseguire un'immagine di finestra mobile in Xephyr che risolve questo problema.

  • Evita le perdite di sicurezza X mentre le applicazioni docker vengono eseguite in un server X nidificato.
  • MIT-SHM è disabilitato per evitare errori di accesso alla RAM.
  • La sicurezza del contenitore è migliorata con --cap-drop ALL --security-opt no-new-privileges . Anche l'utente del contenitore non è root.
  • Viene creato un cookie X per limitare l'accesso alla visualizzazione Xephyr.

Lo script prevede alcuni argomenti, in primo luogo un gestore di finestre host da eseguire in Xephyr, in secondo luogo un'immagine docker, opzionalmente un terzo comando immagine da eseguire. Per eseguire un ambiente desktop nella finestra mobile, utilizzare ":" anziché un gestore di finestre host.

La chiusura della finestra di Xephyr termina le applicazioni del contenitore docker. La chiusura delle applicazioni dockered chiude la finestra di Xephyr.

Esempi:

  • xephyrdocker "openbox --sm-disable" x11docker/lxde pcmanfm
  • xephyrdocker : x11docker/lxde
  • xephyrdocker xfwm4 --device /dev/snd jess/nes /games/zelda.rom

script xephyrdocker:

#! /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       /etc/passwd file with user entry
# --group-add audio                  Allow 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
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. Must be different from cookie file of client, not sure why
# -extension MIT-SHM             disable MIT-SHM to avoid rendering glitches and bad RAM access (+ instead of - enables it)
# -nolisten tcp                  disable tcp connections for security reasons
# -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



La soluzione fornita su fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker sembra essere un modo semplice per avviare le applicazioni GUI dall'interno dei contenitori (ho provato per Firefox su Ubuntu 14.04) ma ho trovato che è necessaria una piccola modifica aggiuntiva alla soluzione pubblicata dall'autore.

In particolare, per l'esecuzione del contenitore, l'autore ha menzionato:

    docker run -ti --rm \
    -e DISPLAY=$DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    firefox

Ma ho trovato che (basato su un particolare commento sullo stesso sito) che due opzioni aggiuntive

    -v $HOME/.Xauthority:$HOME/.Xauthority

e

    -net=host 

è necessario specificare durante l'esecuzione del contenitore per il corretto funzionamento di firefox:

    docker run -ti --rm \
    -e DISPLAY=$DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -v $HOME/.Xauthority:$HOME/.Xauthority \
    -net=host \
    firefox

Ho creato un'immagine docker con le informazioni su quella pagina e questi risultati aggiuntivi: https://hub.docker.com/r/amanral/ubuntu-firefox/




Se vuoi eseguire un'applicazione GUI senza testa, leggi here . Quello che devi fare è creare un monitor virtuale con xvfb o altro software simile. Questo è molto utile se si desidera eseguire test Selenium, ad esempio con i browser.

Qualcosa non menzionato da nessuna parte è che alcuni software in realtà usano il sand-boxing con contenitori Linux. Ad esempio, Chrome non verrà mai eseguito normalmente se non si utilizza il flag appropriato - --privileged durante l'esecuzione del contenitore.




Mentre la risposta di Jürgen Weigert copre essenzialmente questa soluzione, inizialmente non mi era chiaro cosa si stesse descrivendo lì. Quindi aggiungerò il mio punto di vista, nel caso in cui qualcun altro abbia bisogno di chiarimenti.

Prima di tutto, la documentazione pertinente è la pagina di sicurezza X.

Numerose fonti online suggeriscono di montare il socket unix X11 e il file ~/.Xauthority nel container. Queste soluzioni spesso funzionano per fortuna, senza capire veramente perché, ad esempio, l'utente del contenitore finisce con lo stesso UID dell'utente, quindi non è necessaria l'autorizzazione della chiave magica.

Prima di tutto, il file Xauthority ha la modalità 0600, quindi l'utente del contenitore non sarà in grado di leggerlo a meno che non abbia lo stesso UID.

Anche se copi il file nel contenitore e ne cambi la proprietà, c'è ancora un altro problema. Se si esegue l' xauth list sull'host e sul contenitore, con lo stesso file Xauthority , Xauthority visualizzate voci diverse elencate. Questo perché xauth filtra le voci a seconda di dove viene eseguito.

Il client X nel contenitore (cioè l'app GUI) si comporterà come xauth . In altre parole, non vede il cookie magico per la sessione X in esecuzione sul desktop dell'utente. Invece, vede le voci per tutte le sessioni X "remote" che hai aperto in precedenza (spiegato di seguito).

Quindi, ciò che devi fare è aggiungere una nuova voce con il nome host del contenitore e la stessa chiave esadecimale del cookie host (ovvero la sessione X in esecuzione sul desktop), ad esempio:

containerhostname/unix:0   MIT-MAGIC-COOKIE-1   <shared hex key>

Il problema è che il cookie deve essere aggiunto con xauth add all'interno del contenitore:

touch ~/.Xauthority
xauth add containerhostname/unix:0 . <shared hex key>

Altrimenti, xauth tagga in un modo che viene visto solo al di fuori del contenitore.

Il formato per questo comando è:

xauth add hostname/$DISPLAY protocol hexkey

Dove rappresenta il protocollo MIT-MAGIC-COOKIE-1 .

Nota: non è necessario copiare o collegare .Xauthority nel contenitore. Basta creare un file vuoto, come mostrato, e aggiungere il cookie.

La risposta di Jürgen Weigert aggira questo problema utilizzando il tipo di connessione FamilyWild per creare un nuovo file di autorizzazione sull'host e copiarlo nel contenitore. Si noti che per prima cosa estrae la chiave esadecimale per la sessione X corrente da ~/.Xauthority usando xauth nlist .

Quindi i passaggi essenziali sono:

  • Estrarre la chiave esadecimale del cookie per la sessione X corrente dell'utente.
  • Crea un nuovo file Xauthority nel contenitore, con il nome host del contenitore e la chiave esadecimale condivisa (o crea un cookie con il tipo di connessione FamilyWild ).

Ammetto che non capisco molto bene come funzioni FamilyWild, o come i client xauth o X filtrino le voci dal file Xauthority a seconda di dove vengono eseguite. Ulteriori informazioni su questo sono i benvenuti.

Se si desidera distribuire l'app Docker, è necessario uno script di avvio per l'esecuzione del contenitore che ottiene la chiave esadecimale per la sessione X dell'utente e la importa nel contenitore in uno dei due modi illustrati in precedenza.

Aiuta anche a capire i meccanismi del processo di autorizzazione:

  • Un client X (cioè un'applicazione GUI) in esecuzione nel contenitore cerca nel file Xauthority una voce di cookie che corrisponde al nome host del contenitore e al valore di $DISPLAY .
  • Se viene trovata una voce corrispondente, il client X lo passa con la sua richiesta di autorizzazione al server X, attraverso il socket appropriato nella directory /tmp/.X11-unix montata nel contenitore.

Nota: il socket X11 Unix deve ancora essere montato nel contenitore, altrimenti il ​​contenitore non avrà alcuna route verso il server X. La maggior parte delle distribuzioni disabilita l'accesso TCP al server X per impostazione predefinita per motivi di sicurezza.

Per ulteriori informazioni e per capire meglio come funziona la relazione X client / server, è utile anche esaminare il caso di esempio di inoltro SSH X:

  • Il server SSH in esecuzione su una macchina remota emula il proprio server X.
  • Imposta il valore di $DISPLAY nella sessione SSH in modo che punti al proprio server X.
  • Usa xauth per creare un nuovo cookie per l'host remoto e lo aggiunge ai file Xauthority sia per gli utenti locali che per quelli remoti.
  • Quando le app GUI vengono avviate, parlano al server X emulato di SSH.
  • Il server SSH inoltra questi dati al client SSH sul desktop locale.
  • Il client SSH locale invia i dati alla sessione del server X in esecuzione sul desktop, come se il client SSH fosse effettivamente un client X (ad esempio l'app GUI).
  • Il server X utilizza i dati ricevuti per eseguire il rendering della GUI sul desktop.
  • All'inizio di questo scambio, il client X remoto invia anche una richiesta di autorizzazione, utilizzando il cookie appena creato. Il server X locale lo confronta con la sua copia locale.



Sono in ritardo per la festa, ma per gli utenti Mac che non vogliono seguire il percorso XQuartz, ecco un esempio funzionante che costruisce un'immagine Fedora, con un ambiente desktop (xfce) che usa Xvfb e VNC . È semplice e funziona:

Su un Mac, puoi semplicemente accedervi utilizzando l'applicazione Screen Sharing (predefinita), che si connette a localhost:5901 .

Dockerfile:

FROM fedora

USER root

# Set root password, so I know it for the future
RUN echo "root:password123" | chpasswd

# Install Java, Open SSL, etc.
RUN dnf update -y --setopt=deltarpm=false  \
 && dnf install -y --setopt=deltarpm=false \
                openssl.x86_64             \
                java-1.8.0-openjdk.x86_64  \
                xorg-x11-server-Xvfb       \
                x11vnc                     \
                firefox                    \
                @xfce-desktop-environment  \
 && dnf clean all

# Create developer user (password: password123, uid: 11111)
RUN useradd -u 11111 -g users -d /home/developer -s /bin/bash -p $(echo password123 | openssl passwd -1 -stdin) developer

# Copy startup script over to the developer home
COPY start-vnc.sh /home/developer/start-vnc.sh
RUN chmod 700 /home/developer/start-vnc.sh
RUN chown developer.users /home/developer/start-vnc.sh

# Expose VNC, SSH
EXPOSE 5901 22

# Set up VNC Password and DisplayEnvVar to point to Display1Screen0
USER developer
ENV  DISPLAY :1.0
RUN  mkdir ~/.x11vnc
RUN  x11vnc -storepasswd letmein ~/.x11vnc/passwd

WORKDIR /home/developer
CMD ["/home/developer/start-vnc.sh"]

start-vnc.sh

#!/bin/sh

Xvfb :1 -screen 0 1024x768x24 &
sleep 5
x11vnc -noxdamage -many -display :1 -rfbport 5901 -rfbauth ~/.x11vnc/passwd -bg
sleep 2
xfce4-session &

bash
# while true; do sleep 1000; done

Controllare il https://github.com/ddual/docker_recipes#fedora-with-an-x-window-system collegato per https://github.com/ddual/docker_recipes#fedora-with-an-x-window-system ed eseguire comandi se si desidera / necessario.




Le altre soluzioni dovrebbero funzionare, ma ecco una soluzione per docker-compose.

Per correggere quell'errore, devi passare $ DISPLAY e .X11-unix alla finestra mobile, oltre a concedere all'utente che ha avviato l'accesso alla finestra mobile a xhost.

All'interno di docker-compose.yml: version: '2' services: node: build: . container_name: node environment: - DISPLAY volumes: - /tmp/.X11-unix:/tmp/.X11-unix

Nel terminale o nello script:

  • xhost +si:localuser:$USER
  • xhost +local:docker
  • export DISPLAY=$DISPLAY
  • docker-compose up



Docker con rete BRIDGE. per Ubuntu 16.04 con display manager lightdm:

cd /etc/lightdm/lightdm.conf.d
sudo nano user.conf

[Seat:*]
xserver-allow-tcp=true
xserver-command=X -listen tcp

puoi usare più permessi privati

xhost +

docker run --volume="$HOME/.Xauthority:/root/.Xauthority:rw" --env="DISPLAY=$HOST_IP_IN_BRIDGE_NETWORK:0" --net=bridge $container_name



Related