User Namespaces: Sharing the Docker UNIX socket
4/20/2016 Note: Please read my follow-up blog post on this topic based on the comments regarding open access to all Docker APIs via the methods discussed below.
A recent question I received asked for ideas on sharing the Docker UNIX socket when you have user namespaces enabled in the Docker daemon. Given sharing the Docker daemon’s UNIX socket is the recommended and preferred method for allowing in-container tools to interact with the Docker daemon, it’s an important question to try and answer.
Why is this a problem?
So, the UNIX socket created by the daemon, located by default at
/var/run/docker.sock, is owned by host root, with docker group ownership. On your host, adding a user to the docker group allows you to have read/write access to the socket for API communication with the daemon. If you use the usual path of mounting the daemon’s UNIX socket in your container (using
-v /var/run/docker.sock:/var/run/docker.sock) when user namespaces are enabled on the daemon, your container’s root uid (or any other container uid/gid) will have no access at all to the UNIX socket. This is because the entire ID space inside the container will not match host root or host group membership in the docker group. Since the access bits on the socket are
rw-rw---- there is no easy solution without making the UNIX socket unprotected via user/group ownership on the host–a bad idea!
What can I do?
There are several potential solutions, but one interesting solution if you are open to having a proxy container running is to use a new feature about to arrive in Docker 1.11, allowing the use of
--privileged with a new parameter,
--userns=host, while user namespaces are enabled on the Docker daemon. [Thanks to Liron Levin of Twistlock for working on this feature and getting it through the review process!]
With this feature, I can run a privileged container alongside my unprivileged containers which need access to the Docker daemon UNIX socket. The privileged container can expose a TCP endpoint only for other containers to use (not portmapped to the host) [Note that this is still accessible to someone with rudimentary Linux skills–see the comments below from Brian Krebs], and use a simple tool like
socat to pass traffic from the TCP endpoint to the mounted UNIX socket.
Rather than explain it fully in this post, I put together a simple GitHub project which contains a
Dockerfile for building a small container with this capability. The
README.md explains how to run it and link to it from other unprivileged containers. The concept is quite simple, but this solution requires only a small change to the containers which need to use the Docker API: setting the
DOCKER_HOST environment variable to point to the proxy/privileged container.
I’m not going to have Docker 1.11 available; what other options do I have?
You could set up a TCP endpoint for your host-located Docker daemon that can be accessed from any container. The downside is that the Docker API is now accessible from any system which has a route to your host. You could protect it with firewall/
iptables rules, but containerizing the proxied TCP endpoint effectively handles this for you. If you want to use a TCP endpoint, make sure you consider using TLS certificate access to the endpoint, which I describe in another post here on my blog.
If you are more adventurous you might be interested in setting up a proxy UNIX socket on the host system which is owned by the remapped root that will be used inside containers. As long as you are sure you will be using root inside the container to access the “proxy” UNIX socket, then we can run a
socat process on the host, telling it the
mode bits to set on the listening UNIX socket. This will pass traffic to the “real” Docker daemon UNIX socket, and of course requires root privileges on the host to do so.
To find the remapped root UID that will be used inside all containers, we simply look at the range assigned to the remapping user—the username provided to
--userns-remap on the Docker daemon invocation. This user will have an entry in
/etc/subuid; the first numeric entry will be the root UID. In my case I can do the following command and see that the
dockremap user (the user created if you provide
default to the
--userns-remap flag) has the following range assigned:
$ cat /etc/subuid dockremap:231072:65536
Now the command shown below will start
socat with a container root-owned socket listening and passing traffic to the real Docker daemon UNIX socket:
sudo socat UNIX-LISTEN:/var/run/docker-userns.sock,user=231072,group=231072,mode=0600,fork \ UNIX-CLIENT:/var/run/docker.sock
I can now mount the “userns” socket file into an unprivileged container and have full access to the Docker API:
$ docker run -ti -v /var/run/docker-userns.sock:/var/run/docker.sock dockerclient docker version Client: Version: 1.10.0-dev API version: 1.22 Go version: go1.5.2 Git commit: 8ed14c2 Built: Wed Dec 9 01:04:08 2015 OS/Arch: linux/amd64 Experimental: true Server: Version: 1.11.0-rc3 API version: 1.23 Go version: go1.5.3 Git commit: eabf97a Built: Fri Apr 1 22:26:46 2016 OS/Arch: linux/amd64
socat proxy on the host is allowing us access to the Docker API via a socket owned by the remapped root inside the container.
In conclusion, there are a few reasonable ways to handle restricted access to the Docker daemon UNIX socket when user namespaces are enabled for containers. I would be interested if anyone else has other ideas or solutions; send me feedback in the comments section below!
Do you have other questions about practical use of user namespaces in Docker? I’d be happy to do a continuing “user namespaces Q&A” series here on my blog. Post your questions in the comments or point me at your question on Twitter.