As I work more and more with containers, I find myself wanting to make more use of them to segregate running third party apps. Taking the lead of Jessie Frazelle I figured I would try to run the Minecraft client in a Container on Fedora 25. As expected, it was a learning experience, but I got it. Here’s the summary:
I started with Wakaru Himura’s docker-minecraft-client Dockerfile. which was written for Ubuntu. When it didn’t work for me, I started trying for a Fedora based one. It took a couple iterations.
The error indicated that the container was having trouble connecting to, or communicating with, the Unix domain socket used by the X server. It was returjned bythe Java code, and here is an abbreviated version of the stack trace.
You can customize the options to run it in the run.sh and rebuilding the image No protocol specified Exception in thread "main" java.lang.InternalError: Can't connect to X11 window server using ':1' as the value of the DISPLAY variable. at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method) at sun.awt.X11GraphicsEnvironment.access$200(X11GraphicsEnvironment.java:65) at sun.awt.X11GraphicsEnvironment$1.run(X11GraphicsEnvironment.java:110) at java.security.AccessController.doPrivileged(Native Method) at sun.awt.X11GraphicsEnvironment.(X11GraphicsEnvironment.java:74) . . . at net.minecraft.bootstrap.Bootstrap.main(Bootstrap.java:378)
Wakaru’s version uses the Oracle JDK. I’ve been running the OpenJDK from Fedora with Minecraft with few problesm, so I simplified the Java install.
I ended up doing a lot of trial and error to get the X authorization code to find the right information from the parent. One thing I tried was the creation of a user inside the container, to mirror my account outside the container, using Wakaru’s code, but modifying it for my personal account.
FROM index.docker.io/fedora:25 MAINTAINER Adam Young <adam@younglogic.com> RUN dnf -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel java-1.8.0-openjdk-headless RUN dnf -y install strace xorg-x11-xauth RUN dnf -y clean all COPY Minecraft.jar ./ RUN export uid=14370 gid=14370 && \ mkdir -p /home/ayoung && \ echo "ayoung:x:${uid}:${gid}:ayoung,,,:/home/ayoung:/bin/bash" >> /etc/passwd && \ echo "ayoung:x:${uid}:" >> /etc/group && \ chown ${uid}:${gid} -R /home/ayoung CMD XAUTHORITY=~/.Xauthority /usr/bin/java -jar ./Minecraft.jar
Running that still gave the error from the X server connection. THe audit log shows that SELinux was denying the connection.
$ sudo tail -f /var/log/audit/audit.log | grep avc type=AVC msg=audit(1485401508.386:1616): avc: denied { connectto } for pid=18405 comm="java" path=002F746D702F2E5831312D756E69782F5831 scontext=system_u:system_r:container_t:s0:c151,c769 tcontext=unconfined_u:unconfined_r:xserver_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0
Disable SELinux (For now) to see if we can get a success.
sudo setenforce permissive
And run like this
docker run -ti --rm -e DISPLAY --user ayoung:ayoung -v /run/user/14370/gdm/Xauthority:/run/user/14370/gdm/Xauthority -v /tmp/.X11-unix:/tmp/.X11-unix -v /home/ayoung/.Xauthority:/home/ayoung/.Xauthority --net=host minecraft
Et, Viola!:
OK. Let’s deal with SELinux. First, re-enable, and confirm.
$ sudo setenforce enforcing $ getenforce Enforcing
Let’s break apart the AVC: Gentoo’s page has a good summary of the pieces:
Log part | Meaning |
---|---|
denied { connectto } | The attempt to connect to was denied |
for pid=18405 | The ID of the process that triggered the AVC. Not useful now, as the process has since been terminated. |
comm=”java” | The command exec’ed by the process that triggered the AVC. This comes from the line run in the container: CMD XAUTHORITY=~/.Xauthority /usr/bin/java -jar ./Minecraft.jar |
path=002F746D702F2E5831312D756E69782F5831 | path=/tmp/.X11-unix/X1 in Hex. Can be deciphered using: /bin/python3 -c ‘print(bytearray.fromhex(“‘$1′”).decode())’ |
scontext= | Security context of the process. IN parts… |
— system_u: | The system user, since dockerd is running from systemd |
— container_t: | The container target. Docker specific resources have this label |
— s0: | User is Level 0 |
— c151,c769 | User context. |
tcontext= | The security context of the target unix Domain Socket |
— unconfined_u: | Unconfined User. Me. since it was run from me booting my system, which came from a user prompt. |
— unconfined_r: | Unconfied Role. |
— xserver_t: | X server lable, to keep all of X’s resources labeled the same way. |
— s0-s0: | Target is Level 0 |
— c0.c1023 | Target is Context Subcontext. |
tclass=unix_stream_socket | Target Class shows it is a Domain socket |
permissive=0 | SELinux was not running in permissive mode. |
More information on the Levels and contexts is available for those who wish to understand them, but I didn’t need them for this. They are used by other access control tools, and we are not going to bother with it for the Fedora desktop system.
When dealing with SELinux problems, we have a couple tools in the toolkit.
- We can change the context of the caller
- We can change the labels
- we can change the policy
Of the three, changing the policy is most common. We don’t want to break existing policy, so we need a new rule that says that containers can talk to domains sockets for the xserver. That policy looks like this:
(allow container_t xserver_t (unix_stream_socket (connectto)))
Else where, we’ve seen that the connection reads the X rules in the Xauthority file, which I have pointing to ~/.Xauthority so a second rule makes that part happy. Here is my complete mycontainer.cil file
(allow container_t xserver_t (unix_stream_socket (connectto))) (allow container_t user_home_t (dir (read)))
Add that to the systems policy with:
sudo semodule -i mycontainer.cil
Re-enable SELinux enforeing and run the docker file, and it all works.
It took a lot of troubleshooting to get to that point. Special thanks to grift in #selinux for helping with the policy.
Below this is my raw notes and logs, mostly kept for my own historical perspective. There were a few comasnds I used that I will want to look at again. I have the IORC log and out put from more commands below, too.
The file in question is:
$ ls -Z /tmp/.X11-unix/ system_u:object_r:user_tmp_t:s0 X0 unconfined_u:object_r:user_tmp_t:s0 X1
And we’ll relabel X1. Since this is a test, we’ll do a temporary relabel. Worst case, everything locks up and we have to reboot.
First I better save this post. I might get locked out….
$ sudo chcon -t container_t /tmp/.X11-unix/X1 [sudo] password for ayoung: chcon: failed to change context of '/tmp/.X11-unix/X1' to ‘unconfined_u:object_r:container_t:s0’: Permission denied
Hmmm. In the Audit log:
type=AVC msg=audit(1485434178.292:1753): avc: denied { relabelto } for pid=28244 comm="chcon" name="X1" dev="tmpfs" ino=47315 scontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tcontext=unconfined_u:object_r:container_t:s0 tclass=sock_file permissive=0
To test, I am going to disable SELinux, relabel, then re-enable it.
$ chcon -t container_t /tmp/.X11-unix/X1 $ sudo ls -Z /tmp/.X11-unix/ system_u:object_r:user_tmp_t:s0 X0 unconfined_u:object_r:container_t:s0 X1 $ sudo setenforce enforcing $ docker run -ti --rm -e DISPLAY --user ayoung:ayoung -v /run/user/14370/gdm/Xauthority:/run/user/14370/gdm/Xauthority -v /tmp/.X11-unix:/tmp/.X11-unix -v /home/ayoung/.Xauthority:/home/ayoung/.Xauthority --net=host minecraft Exception in thread "main" java.awt.AWTError: Can't connect to X11 window server using ':1' as the value of the DISPLAY variable.
and in the audit log:
type=AVC msg=audit(1485434417.111:1789): avc: denied { connectto } for pid=28511 comm="java" path=002F746D702F2E5831312D756E69782F5831 scontext=system_u:system_r:container_t:s0:c124,c220 tcontext=unconfined_u:unconfined_r:xserver_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0
It still has the xserver_t label. Looking at it via ls:
$ sudo ls -Z /tmp/.X11-unix/ ls: cannot access '/tmp/.X11-unix/X1': Permission denied system_u:object_r:user_tmp_t:s0 X0 (null) X1
Ooops. Probably got lucky there we didn’t crash. Lets reset it,
$ sudo setenforce permissive $ sudo ls -Z /tmp/.X11-unix/ system_u:object_r:user_tmp_t:s0 X0 unconfined_u:object_r:container_t:s0 X1 $ chcon -t user_tmp_t /tmp/.X11-unix/X1 $ sudo ls -Z /tmp/.X11-unix/ system_u:object_r:user_tmp_t:s0 X0 unconfined_u:object_r:user_tmp_t:s0 X1 $ sudo setenforce enforcing $ sudo ls -Z /tmp/.X11-unix/ system_u:object_r:user_tmp_t:s0 X0 unconfined_u:object_r:user_tmp_t:s0 X1
Since we can’t do ls, it is safe to assume that user launched X processes will also not be able to connect to the socket. But, since the labels don’t match, I am going to assume, also, that we are looking at the wrong file. The target that was denied had a label of xserver_t, and this had user_tmp_t.
Perhaps it was something in /run/user/14370/gdm/Xauthority ? Let’s look.
$ ls -Z /run/user/14370/gdm/Xauthority unconfined_u:object_r:user_tmp_t:s0 /run/user/14370/gdm/Xauthority
Nope. That path=002F746D702F2E5831312D756E69782F5831 must be pointing somewhere else. Let’s see if it is an inode.
$ find / -inum 002F746D702F2E5831312D756E69782F5831 find: invalid argument `002F746D702F2E5831312D756E69782F5831' to `-inum'
Nope. What is it?
$ sudo netstat -xa | grep X11-unix unix 2 [ ACC ] STREAM LISTENING 29431 @/tmp/.X11-unix/X0 unix 2 [ ACC ] STREAM LISTENING 47313 @/tmp/.X11-unix/X1 unix 2 [ ACC ] STREAM LISTENING 47314 /tmp/.X11-unix/X1 unix 2 [ ACC ] STREAM LISTENING 29432 /tmp/.X11-unix/X0 unix 3 [ ] STREAM CONNECTED 1612587 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 40628 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 42628 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 37201 @/tmp/.X11-unix/X0 unix 3 [ ] STREAM CONNECTED 30693 @/tmp/.X11-unix/X0 unix 3 [ ] STREAM CONNECTED 395122 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 38481 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 1614636 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 38798 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 1714036 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 353557 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 30688 @/tmp/.X11-unix/X0 unix 3 [ ] STREAM CONNECTED 42436 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 36697 @/tmp/.X11-unix/X0 unix 3 [ ] STREAM CONNECTED 42430 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 42363 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 37936 @/tmp/.X11-unix/X0 unix 3 [ ] STREAM CONNECTED 3713614 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 55540 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 48315 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 38629 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 3204892 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 49616 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 1612589 @/tmp/.X11-unix/X1 unix 3 [ ] STREAM CONNECTED 1709056 @/tmp/.X11-unix/X1
Resorting to IRC, got some help in #selinux:
grift: path=/tmp/.X11-unix/X1 /bin/python3 -c 'print(bytearray.fromhex("'$1'").decode())' thats now selinux deals with stream connect the "connectto" check is done on the process listening on the socket basically stream connect/dgram sendto is a two step thing 1. step on connectto sendto process listening on the socket respectively 2. step two writing the actual sock file allow container_t xserver_t:unix_stream_socket connectto; allow container_t user_tmp_t:sock_file write you might want to run semodule -DB before you try it fedora is kind of quick to hide events
Here is some of the output from the audit log:
type=AVC msg=audit(1485447098.800:1983): avc: denied { connectto } for pid=1455 comm="java" path=002F746D702F2E5831312D756E69782F5831 scontext=system_u:system_r:container_t:s0:c250,c486 tcontext=unconfined_u:unconfined_r:xserver_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=1 type=AVC msg=audit(1485447098.800:1984): avc: denied { read } for pid=1455 comm="java" name=".Xauthority" dev="dm-1" ino=393222 scontext=system_u:system_r:container_t:s0:c250,c486 tcontext=system_u:object_r:user_home_t:s0 tclass=dir permissive=1
The convo continued:
grift: echo "(allow container_t xserver_t (unix_stream_socket (connectto)))" > mycontainer.cil theres another one that might be related (or not) where "java" lists ~/.Xauthority echo " (allow container_t user_home_t (dir (read)))" >> mycontainer.cil && semodule -i mycontainer.cil run semodule -B to hide it again if everything works now for you