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!
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