
Traefik is a great reverse proxy solution, and a perfect tool to direct traffic in container environments. However, to do that, it needs access to docker – and that is very dangerous and must be tightly secured!
The problem: access to the docker socket
Containers offer countless opportunities to improve the deployment and management of services. However, having multiple containers on one system, often re-deploying them on the fly, requires a dynamic way of routing traffic to them. Additionally, there might be reasons to have a front end reverse proxy to sort the traffic properly anyway.
In comes traefik – “the cloud native edge router”. Among many supported backends it knows how to listen to docker and create dynamic routes on the fly when new containers come up.
To do so traefik needs access to the docker socket. Many people decide to just provide that as a volume to traefik. This usually does not work because SELinux prevents it for a reason. The apparent workaround for many is to run traefik in a privileged container. But that is a really bad idea:
Docker currently does not have any Authorization controls. If you can talk to the docker socket or if docker is listening on a network port and you can talk to it, you are allowed to execute all docker commands. […]
http://www.projectatomic.io/blog/2014/09/granting-rights-to-users-to-use-docker-in-fedora/
At which point you, or any user that has these permissions, have total control on your system.
The solution: a docker socket proxy
But there are ways to securely provide traefik the access it needs – without exposing too much permissions. One way is to provide limited access to the docker socket via tcp via another container which cannot be reached from the outside that easily.
Meet Tecnativa’s docker-socket-proxy:
What?
https://github.com/Tecnativa/docker-socket-proxy/blob/master/README.md
This is a security-enhaced proxy for the Docker Socket.
Why?
Giving access to your Docker socket could mean giving root access to your host, or even to your whole swarm, but some services require hooking into that socket to react to events, etc. Using this proxy lets you block anything you consider those services should not do.
It is a container which connects to the docker socket and exports the API features in a secured and configurable way via TCP. At container startup it is configured with booleans to which API sections access is granted.
So basically you set up a docker proxy to support your proxy for docker containers. Well…
How to use it
The docker socket proxy is a container itself. Thus it needs to be launched as a privileged container with access to the docker socket. Also, it must not publish any ports to the outside. Instead it should run on a dedicated docker network shared with the traefik container. The Ansible code to launch the container that way is for example:
- name: ensure privileged docker socket container
docker_container:
name: dockersocket4traefik
image: tecnativa/docker-socket-proxy
log_driver: journald
env:
CONTAINERS: 1
state: started
privileged: yes
exposed_ports:
- 2375
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:z"
networks:
- name: dockersocket4traefik_nw
Note the env
right in the middle: that is where the exported permissions are configured. CONTAINERS: 1
provides access to container relevant information. There are also SERVICES: 1
and SWARM: 1
to manage access to docker services and swarm.
Traefik needs to have access to the same network. Also, the traefik configuration needs to point to the docker container via tcp:
[docker]
endpoint = "tcp://dockersocket4traefik:2375"
Conclusion
This setup works surprisingly easy. And it allows traefik to access the docker socket for the things it needs without exposing critical permissions to take over the system. At the same time, full access to the docker socket is restricted to a non-public container, which makes it harder for attackers to exploit it.
If you have a simple container setup and use Ansible to start and stop the containers, I’ve written a role to get the above mentioned setup running.