Secure Docker Containers w/X-11 (with no --privileged and no --net=host)

In this article we go over securing docker containers, and getting X-11 enabled without resorting to root!

Secure Docker Containers w/X-11 (with no --privileged and no --net=host)

The big dilemma with docker containers was two-fold:

  • Getting X-11 to run at all (many suggest the inefficient VNC or worse xhost+)
  • vnc connections are sloww in comparison to X-11.
  • xhost+ is very insecure allowing anyone to access.
  • When one did get X-11 to run many resorted to --privileged and --net=host. This presented more problems because if --net=host the docker container then masqueraded into the IP of the host system blending ports..  Add in several containers and you have a mess.  Not only that but --net=host also disabled port mapping.
  • Security was also not addressed.

Solutions: Part 1: Getting X-11 to work:

  • Make a directory and inside it create a Dockerfile, put this:
FROM ubuntu:latest
ENV RED='\e[1;33m'
ENV CLR='\e[0m'

RUN printf "${RED}RUN apt-get update ${CLR}\n"
RUN sleep 2
RUN apt-get update

RUN printf "${RED} RUN ap-get install -y wget build-essential checkinstall${CLR}\n"
RUN sleep 2
RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y xserver-xorg-core
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y xserver-xorg
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y wget build-essential checkinstall nmap
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y xauth
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y openbox
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y xorg
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y systemctl
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-client
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y iproute2

Of note: In this part of the Dockerfile it adds two users, root with default password Docker! and docker with default password Docker! however the key commands are the 'RUN chage -d 0 docker' and 'RUN chage -d 0 root'  - on first activation of the container they will be required to change their password.


# Add in root:Docker!  and docker:Docker!
RUN echo "root:Docker!" | chpasswd
RUN adduser docker
RUN echo "docker:Docker!" | chpasswd

# Force a password expiry immediately on first login
RUN chage -d 0 root
RUN chage -d 0 docker

Now we need to get rid of the requirement for --privileged and --net=host. Ironcally guides like here, here, and so forth.  It turns out none of that is needed:

Inside your sshd_config simply change:

#X11UseLocalhost yes
# to
X11UseLocalhost no

When you build the container and run it - no longer is --net=host or --privileged required - not only that but now your -p 2000:222 option is reenabled. However you must login as root one time and set your DISPLAY=<MAIN SERVER IP>:DISPLAY as in:

DISPLAY=192.168.2.21:0.0

Once this is done - it will be recogized by the docker user on the second login attempt (as the first will require a password reset).

Will it survive a docker container shutdown and restart? Yes.

docker stop <container>
docker start <container>

A full docker build for netbeans with cmake!

ENV RED='\e[1;33m'
ENV CLR='\e[0m'

RUN printf "${RED}RUN apt-get update ${CLR}\n"
RUN apt-get update

RUN printf "${RED} RUN ap-get install -y wget build-essential checkinstall${CLR}\n"
RUN DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y xserver-xorg-core xserver-xorg wget build-essential nmap xauth
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y xorg systemctl openssh-server openssh-client iproute2 libssl-dev


RUN wget https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0.tar.gz
RUN tar -xvf cmake-3.20.0.tar.gz
WORKDIR cmake-3.20.0
RUN ./bootstrap
RUN make && make install

# Add in root:Docker!  and docker:Docker!
RUN echo "root:Docker!" | chpasswd
RUN adduser docker
RUN echo "docker:Docker!" | chpasswd

# Force a password expiry immediately on first login
RUN chage -d 0 root
RUN chage -d 0 docker

RUN printf "${RED} RUN apt-get install -y software-properties-common${CLR}\n"
RUN sleep 2
RUN apt-get -y update
RUN apt-get install -y software-properties-common --fix-missing
RUN apt-get install -y apt-utils
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get -y update
RUN mkdir -p /files
COPY sshd_config /etc/ssh/

#Add in netbeans here

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y default-jre default-jdk dpkg
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libc6-x32 zip unzip nano vim
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libc6-i386

COPY *.deb /files/

RUN DEBIAN_FRONTEND=noninteractive dpkg -i /files/jdk.deb
RUN DEBIAN_FRONTEND=noninteractive add-apt-repository "deb http://archive.ubuntu.com/ubuntu focal universe"
RUN DEBIAN_FRONTEND=noninteractive apt update
RUN DEBIAN_FRONTEND=noninteractive dpkg -i /files/apache.deb

# Add in scp rsync
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y rsync curl

ENTRYPOINT service ssh restart && bash

Wait? What about explicit containers that require user interaction to setup - the one-time 'chage -d 0 root' and 'chage -d 0 docker' are passed?

Easy.  Once you have 'manually tuned' your container and snapshot it to an image with:

docker commit <my container> <my image>

Then from that image we build another layer - create a new directory and simply put a Dockerfile in with:

FROM <my fresh snapshot image>
# Force a password expiry immediately on first login
RUN echo "root:Docker!" | chpasswd
RUN echo "docker:Docker!" | chpasswd
RUN chage -d 0 root
RUN chage -d 0 docker
ENTRYPOINT service ssh restart && bash
Linux Rocks Every Day