Docker to run X applications while connected through SSH - ssh

I have used these instructions for Running Gui Apps with Docker to create images that allow me to launch GUI based applications.
It all works flawlessly when running Docker on the same machine, but it stops working when running it on a remote host.
Locally, I can run
docker --rm --ti -e DISPLAY -e <X tmp> <image_name> xclock
And I can get xclock running on my host machine.
When connecting remotely to a host with XForwarding, I am able to run X applications that show up on my local X Server, as anyone would expect.
However if in the remote host I try to run the above docker command, it fails to connect to the DISPLAY (usually localhost:10.0)
I think the problem is that the XForwarding is setup on the localhost interface of the remote host.
So the docker host has no way to connect to DISPLAY=localhost:10.0 because that localhost means the remote host, unreachable from docker itself.
Can anyone suggest an elegant way to solve this?
Regards
Alessandro
EDIT1:
One possible way I guess is to use socat to forward the remote /tmp/.X11-unix to the local machine. This way I would not need to use port forwarding.
It also looks like openssh 6.7 will natively support unix socket forwarding.

When running X applications through SSH (ssh -X), you are not using the /tmp/.X11-unix socket to communicate with the X server. You are rather using a tunnel through SSH reached via "localhost:10.0".
In order to get this to work, you need to make sure the SSH server supports X connections to the external address by setting
X11UseLocalhost no
in /etc/ssh/sshd_config.
Then $DISPLAY inside the container should be set to the IP address of the Docker host computer on the docker interface - typically 172.17.0.1. So $DISPLAY will then be 172.17.0.1:10
You need to add the X authentication token inside the docker container with "xauth add" (see here)
If there is any firewall on the Docker host computer, you will have to open up the TCP ports related to this tunnel. Typically you will have to run something like
ufw allow from 172.17.0.0/16 to any port $TCPPORT proto tcp
if you use ufw.
Then it should work. I hope it helps. See also my other answer here https://stackoverflow.com/a/48235281/5744809 for more details.

Related

X11 forwarding of a GUI app running in docker

First off: I have read the answers to similar questions on SO, but none of them worked.
IMPORTANT NOTE: The answer below is still valid, but maybe jump to the end for an alternative.
The situation:
App with GUI is running in a docker container (CentOS 7.1) under Arch Linux. (machine A)
Machine A has a monitor connected to it.
I want to access this GUI via X11 forwarding on my Arch Linux client machine. (machine B)
What works:
GUI works locally on machine A (with /tmp/.X11-unix being mounted in the Docker container).
X11 forwarding of any app running outside of docker (X11 forwarding is set up and running properly for non-docker usage).
I can even switch the user while remotely logged in, copy the .Xauthority file to the other user and X11 forwarding works as well.
Some setup info:
Docker networking is 'bridged'.
Container can reach host (firewall is open).
DISPLAY variable is set in container (to host-ip-addr:10.0 because of TCP port 6010 where sshd is listening).
Packets to X forward port (6010) are reaching the host from the container (tcpdump checked).
What does not work:
X11 forwarding of the Docker app
Errors:
X11 connection rejected because of wrong authentication.
xterm: Xt error: Can't open display: host-ip-addr:10.0
Things i tried:
starting client ssh with ssh -Y option on machine B
putting "X11ForwardTrusted yes" in ssh_config on machine B
xhost + (so allow any clients to connect) on machine B
putting Host * in ssh_config on machine B
putting X11UseLocalhost no in sshd_config on machine A (to allow non-localhost clients)
Adding the X auth token in the container with xauth add from the login user on machine A
Just copying over the .Xauthority file from a working user into the container
Making shure .Xauthority file has correct permissions and owner
How can i just disable all the X security stuff and get this working?
Or even better: How can i get it working with security?
Is there at least a way to enable extensive debugging to see where exactly the problem is?
Alternative: The first answer below shows how to effectively resolve this issue. However: I would recommend you to look into a different approach all together, namely VNC. I personally switched to a tigerVNC setup that replaces the X11 forwarding and have not looked back. The performance is just leagues above what X11 forwarding delivered for me. There might be some instances where you cannot use VNC for whatever reason, but i would try it first.
The general setup is now as follows:
-VNC server runs on machine A on the host (not inside a docker container).
-Now you just have to figure out how to get a GUI for inside a docker container (which is a much more trivial undertaking).
-If the docker container was started NOT from the VNC environment, the DISPLAY variable maybe needs ajdusting.
Thanks so much #Lazarus535
I found that for me adding the following to my docker command worked:
--volume="$HOME/.Xauthority:/root/.Xauthority:rw"
I found this trick here
EDIT:
As Lazarus pointed out correctly you also have to set the --net=host option to make this work.
Ok, here is the thing:
1) Log in to remote machine
2) Check which display was set with echo $DISPLAY
3) Run xauth list
4) Copy the line corresponding to your DISPLAY
5) Enter your docker container
6) xauth add <the line you copied>*
7) Set DISPLAY with export DISPLAY=<ip-to-host>:<no-of-display>
*so far so good right?
This was nothing new...however here is the twist:
The line printed by xauth list for the login user looks something like this (in my case):
<hostname-of-machine>/unix:<no-of-display> MIT-MAGIC-COOKIE-1 <some number here>
Because i use the bridged docker setup, the X forwarding port is not listening locally, because the sshd is not running in the container. Change the line above to:
<ip-of-host>:<no-of-display> MIT-MAGIC-COOKIE-1 <some number here>
In essence: Remove the /unix part.
<ip-of-host> is the IP address where the sshd is running.
Set the DISPLAY variable as above.
So the error was that the DISPLAY name in the environment variable was not the "same" as the entry in the xauth list / .Xauthority file and the client could therefor not authenticate properly.
I switched back to an untrusted X11 forwarding setting.
The X11UseLocalhost no setting in the sshd_config file however is important, because the incomming connection will come from a "different" machine (the docker container).
This works in any scenario.
Install xhost if you don't have it. Then, in bash,
export DISPLAY=:0.0
xhost +local:docker
After this run your docker run command (or whatever docker command you are running) with -e DISPLAY=$DISPLAY
It works usually via https://stackoverflow.com/a/61060528/429476
But if you are running docker with a different user than the one used for ssh -X into the server with; then copying the Xauthority only helped along with volume mapping the file.
Example - I sshed into the server with alex user.Then ran docker after su -root and got this error
X11 connection rejected because of wrong authentication.
After copying the .XAuthoirty file and mapping it like https://stackoverflow.com/a/51209546/429476 made it work
cp /home/alex/.Xauthority .
docker run -it --network=host --env DISPLAY=$DISPLAY --privileged \
--volume="$HOME/.Xauthority:/root/.Xauthority:rw" \
-v /tmp/.X11-unix:/tmp/.X11-unix --rm <dockerimage>
More details on wiring here https://unix.stackexchange.com/a/604284/121634
Some clarifying remarks. Host is A, local machine is B
Ive edited this post to note things that I think should work in theory but haven't been tested, vs things I know to work
Running docker non-interactively
If your docker is running not interactively and running sshd, you can use jumphosts or proxycommand and specify the x11 client to run. You should NOT volume share your Xauthority file with the container, and sharing -e DISPLAY likely has no effect on future ssh sessions
Since you essentially have two sshd servers, either of the following should work out of the box
if you have openssh-client greater than version 7.3, you can use the following command
ssh -X -J user-on-host#hostmachine,user-on-docker#dockercontainer xeyes
If your openssh client is older, the syntax is instead
(google says the -X is not needed in the proxy command, but I am suspicious)
ssh -X -o ProxyCommand="ssh -W %h:%p user-on-host#hostmachine" user-on-docker#dockermachine xeyes
Or ssh -X into host, then ssh -X into docker.
In either of the above cases, you should NOT share .Xauthority with the container
Running docker interactively from within the ssh session
The easiest way to get this done is to set --net=host and X11UseLocalhost yse.
If your docker is running sshd, you can open a second ssh -X session on your local machine and use the jumphost method as above.
If you start it in the ssh session, you can either -e DISPLAY=$DISPLAY or export it when you're in. You might have to export it if you attach to an exiting container where this line wasn't used.
Use these docker args for --net host and x11uselocalhost yes
ssh -X to host
-e DISPLAY=$DISPLAY
-v $HOME/.Xauthority:/home/same-as-dash-u-user/.Xauthority
-u user
What follows is explanation of how everything works and other approaches to try
About Xauthority
ssh -X/-Y set up a session key in the hosts Xauthority file, and then sets up a listen port on which it places an x11 proxy that uses the session key, and converts it to be compatible with the key on your local machine. By design, the .Xauthority keys will be different between your local machine and the host machine. If you use jumphosts/proxycommand the keys between the host and the container will yet again be different from each other. If you instead use ssh tunnels or direct X11 connection, you will have to share the host Xauthority with the container, in the case of sharing .Xauthority with the container, you can only have one active session per user, since new sessions will invalidate the previous ones by modifying the hosts .Xauthority such that it only works with that session's ssh x11 proxy
X11UserLocalhost no theory##
Even Though X11UseLocalhost no causes the x server to listen on the wildcard address, With --net host I could not redirect the container display to localhost:X.Y where x and why are from the host $DISPLAY
X11UseLocalhost yes is the easy way
If you choose X11UseLocalhost yes the DISPLAY variable on the host becomes localhost:X:Y, which causes the ssh x11 proxy to listen only on localhost port x.
If X11UseLocalhost is no, the DISPLAY variable on the host becomes the host's hostname:X:Y, which causes the xerver to listen on 0.0.0.0:6000+X and causes xclients to reach out over the network to the hostname specified.
this is theoretical, I don't yet have access to docker on a remote host to test this
But this is the easy way. We bypass that by redirecting the DISPLAY variable to always be localhost, and do docker port mapping to move the data from localhost:X+1.Y on the container, to localhost:X.Y on the host, where ssh is waiting to forward x traffic back to the local machine. The +1 makes us agnostic to running either --net=host or --net=bridge
setting up container ports requires specifying expose in the dockerfile and publishing the ports with the -p command.
Setting everything up manually without ssh -X
This works only with --net host. This approach works without xauth because we are directly piping to your unix domain socket on the local machine
ssh to host without -X
ssh -R6010:localhost:6010 user#host
start docker with -e DISPLAY=localhost:10.1 or export inside
in another terminal on local machine
socat -d -d TCP-LISTEN:6010,fork UNIX-CONNECT:/tmp/.X11-unix/X0
In original terminal run xclients
if container is net --bridged and you can't use docker ports, enable sshd on the container and use the jumphosts method

Privoxy + socks5 tunnel on remote host

I'd like to run privoxy on a remote machine to remove adds when I browse the web from my laptop. My plan is to use socks5 tunnel from my local machine, pointing to an instance hosted in Google Cloud.
ssh -D 1080 -fCNq me#server.com
I have configured macOS to use 127.0.0.1:1080 as the socks proxy successfully and can access the internet. So far so good. I've also installed privoxy on the server and started it on 127.0.0.1:8118.
sudo yum install privoxy -y
sudo service privoxy start
However, I can't figure out how to connect my tunneled traffic to privoxy. How do I get all new connections after the socks connection on the server to go through privoxy?
Here's my suggestion.
On remotemachine, run privoxy listening on 127.0.0.1:8118, just as you describe.
On localmachine, run a command like the following. Authenticate as you normally would, either via password or via ssh key. (If at some point you decide that you want to do it without human intervention, you may need to use a passphraseless key, perhaps under a user created just for this purpose.) (Note that the localhost in the below refers to localhost from remotemachine's point of view!)
ssh -vnNf -L 1080:localhost:8118 remotemachine
Configure your browser on localmachine to use localhost:1080 as a socks proxy.
PROFIT
I'm pretty sure this will do the trick, but if I'm missing something please let me know and I'll try to figure it out/fix it.

Docker for Windows with existing hyper-v virtual machine

I have the following setup:
A Windows 10 Pro Laptop ("Win10Laptop") that has a Windows 10 Pro VM ("Win10VM") running on Hyper-V. I have created an nginx container by running the following command on the host machine:
docker run -d -p 80:80 --name webserver nginx
While the container is running I can access http://localhost from Win10Laptop and this works fine. My question is what do I need to configure to access nginx from Win10VM? Win10VM has only one network adaptor which is configured to use the "External" Vswitch connected to my Wifi interface.
Let me know if you need any more details. I've tried all sorts and can't figure it out!
Thanks,
Michael
You need to connect to the IP the VM has acquired on the External switch. Run ipconfig inside the VM to see what IP it has, then open http://<vm-ip> from your host.

can't access apache on docker from my localhost

I've been following this tutorial for beginners about docker which basically instructs you to create an apache container and map a localhost port to the one on the container.
when I try localhost:80 it doesn't connect, although the container is up and running.
I even made a rule in the firewall to allow connection to port 80, but couldn't get connected to the localhost.
Any ideas ?
On Windows/OS X, Docker is running inside a Linux virtual machine (Docker Toolbox) with a default IP address of 192.168.99.100. Thus, when you use docker run -p 80:80 to bind the container port to host port, it in fact binds to the virtual machine's port 80. Thus the address you need is http://192.168.99.100.
The 172.17.0.3 address is the address of the docker container inside that virtual machine, and is not accessible directly from Windows/OS X.
Add a line to your DockerFile before restarting apache.
RUN echo 'ServerName localhost' >> /etc/apache2/apache2.conf
I stumbled upon this question as I was looking for a way to bind my local HTTP port (80) to the HTTP port of my container, an Apache container running on Docker Desktop for Windows - through WSL2 (this is important)
I couldn't find a quick and easy way to do this, so I figured it out myself.
What you must do is bind your local port (on Windows) to the port on WSL.
Here is how I did it :
$wsl_ip = (wsl -d "docker-desktop" -- "ifconfig" "eth0" "|" "grep" "inet addr:").trim("").split(":").split()[2]
netsh interface portproxy add v4tov4 listenport=443 listenaddress=0.0.0.0 connectport=443 connectaddress=$wsl_ip
netsh interface portproxy add v4tov4 listenport=80 listenaddress=0.0.0.0 connectport=80 connectaddress=$wsl_ip
You can either create a Powershell Script (.ps1) and run it with Powershell, or copy/paste each command line into Windows Terminal / Powershell running with Administrator Privileges.
What this does is :
attach to the "docker-desktop" distribution running in WSL2 2
run "ifconfig eth0 | grep inet addr:" to get the local IP address of
the "virtual machine"
parse the result, and use Netsh to
create a portproxy between port 80 of your Windows machine and port
80 of your Linux machine. Same is done for port 443. You can easily
map other ports if you understand what the command is doing.
More explanation :
Since Docker for Windows 10/11 uses WSL2, when you expose a port (through docker-compose or with an EXPOSE command in your Dockerfile), it is exposed to a Linux Distribution called "docker-desktop" that is ran with WSL2. For some reason, ports 80 and 443 that are exposed from a container are NOT forwarded to the host.
The official documentation acknoledges some issues but their solution is just to use another port (for example, 8080 mapped to 80).
Issues with this method :
Each time you reboot your system (or WSL2), the Linux machine gets assigned a new IP and you have to do it again. What you could do is setup a command to run when your container starts that connects through ssh to the host and runs the script, but I'm too lazy to have done it myself.

how to create reverse ssh tunnel to docker-toolbox which binds to 0.0.0.0

I recently started using docker toolbox on windows and its been quite a hard experience. A lot of things are different compared to running docker on linux as here docker is actually running inside virtual machine.
one of the issues i was still not able to resolve is how to create reverse ssh tunnel to my docker-toolbox VM and bind it to 0.0.0.0 (or to docker0 interface)
i have docker toolbox 1.10.2 and am running windows 7.
motivation: I have mysql server running on my windows machine and i would like to connect to it from my docker containers.
problem: i first tried connecting directly to 192.168.99.1 (the ip of my host windows box inside my containers) ... however that doesnt work. i think the reason is that my windows box is blocking all external connections to it. i cant change windows firewall settings as those are managed by domain.
workaround: i setup ssh connection to my VM (boot2docker inside virtualbox) with putty and i try to setup reverse proxy:
i check the "Remote ports do the same" under SSH tunnels in putty
source port i enter 0.0.0.0:3306
destination i enter localhost:3306
i check "remote" radio button
i then ssh to boot2docker ... netstat -natp | grep 3306 shows me its actually bound to 127.0.0.1
i opened /usr/local/etc/ssh/sshd_config
i set GatewayPorts yes
i restard sshd with /usr/local/etc/init.d/openssh restart
i get disconnected ... when i create new connection and netstat -natp | grep 3306 i see 3306 is still bound to 127.0.0.1 only
same settings in putty (and in sshd_config) work on my debian box ... but not inside boot2docker.
any idea whats wrong ? thanks a lot.