Les images de conteneurs Docker qui s'exécutent avec des droits non root
ajoutent une couche de sécurité supplémentaire.
Comment lancer notre instance de Traefik dans un environnement Docker avec un utilisateur sans privilège?
Un container non-root c'est quoi ? Est-ce utile ?
Non-root container
Tout container qui se lance va exécuter un programme. Ce programme va s'exécuter avec le PID 1
, par défaut Docker va exécuter cette tâche avec le compte root
. Cela pose un problème que tout Administrateur systèmes devrait avoir en tête : le principe du moindre privilège.
J'ai bien conscience qu'une partie d'entre vous, n'est pas SysAdmin, mais si vous vous définissez comme DevOps alors cette notion doit faire partie de vos mantras ! Elle n'est pas toujours applicable facilement, mais dès que possible il faut l'envisager !
En quoi cela pose problème ?
Actuellement lancer son container en tant que root
est - hélas - normal. Mais si vous souhaitez sécuriser vos containers, alors il s'agit d'un élément à prendre en compte. Au moins, comme évoqué précédemment au nom du moindre privilège. Mais également pour des raisons de sécurité déjà évoquées sur ce blog :
![](/content/images/2020/06/sergio-souza-V2cASd1teqE-unsplash.jpg)
Je vous invite également à lire l'excellent article à ce sujet de Bryant Hagadorn :
Bien évidement cette étape ne sera peut-être pas votre priorité. Et il sera sûrement nécessaire d'effectuer quelques tests avec votre container avant d'y arriver.
Et dans le cas de Traefik ? Quel utilisateur est utilisé ?
Traefik
Regardons l'image de notre reverse proxy favori de plus près :
docker exec -it traefik ps aux
Vous constaterez en regardant la sortie de la commande, que l'utilisateur utilisé pour lancer l'exécutable Traefik est l'utilisateur root
.
On pourrait se demander pourquoi Traefik utilise un compte root
pour fonctionner ? Mais il existe de nombreuses raisons dont voici les plus simples :
- L'utilisation de port privilégié ( < 1024 ).
- L'accès à la socket Docker,
Et dans le cas de Nginx par exemple ? De base l'image est également lancée avec le compte root. Par contre les processus workers utilisent un compte non privilégié et nginx
fournit des indications pour lancer son image avec un compte non-root :
It is possible to run the image as a less privileged arbitrary UID/GID...
Il faut se le dire très honnêtement : si ces deux images ne se lancent pas directement avec un compte sans privilège, c'est tout simplement qu'elles ne fonctionneraient pas partout - et pour tous - out-of-the-box !
Et oui, les utilisateurs finaux sont les premiers fautifs 😀
Mais existe t-il des solutions pour changer ça ?
Solution
Il existe même DES solutions ! Je vais en présenter une aujourd'hui, mais il en existe bien d'autres également.
Le but de cet article, et du blog, reste de vous sensibiliser à la sécurité de vos containers et de vous apporter des connaissances afin que vous puissiez réaliser vos propres choix/installations : je ne prétends pas avoir la meilleure solution !
Aujourd'hui, je vais utiliser une option présente dans Docker, le flag --user
:
Cette directive permet d’exécuter le processus lancé par votre container avec l'utilisateur passé en argument.
Pratique donc pour passer d'un utilisateur comme root
à un compte sans privilège. Cette option est également disponible dans le format docker-compose
.
Passons immédiatement à la pratique en créant un fichier docker-compose.yaml
pour lancer Traefik :
version: "3.8"
services:
reverse_proxy:
image: traefik:2.2
user: 1001:1001
restart: unless-stopped
command:
--api.insecure=true
--log.level=DEBUG
--entrypoints.web.address=:80
--providers.docker
--providers.docker.exposedbydefault=false
ports:
# The HTTP port
- "80:80"
# The Admin port
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
networks:
- web
networks:
web:
name: traefik_web
🚩 Attention, ne passez un nom d'utilisateur à l'option user que si celui-ci existe sur votre image. J'utilise ici l'uid/gid 1001 qui n'existe pas sur mon hôte 🚩
Mais en lançant ce container on remonte très rapidement une erreur :
# sudo docker-compose up
Creating network "traefik_web" with the default driver
Creating traefik_reverse_proxy_1 ... done
Attaching to traefik_reverse_proxy_1
....
reverse_proxy_1 | 2020/07/15 11:37:23 traefik.go:72: command traefik error: error while building entryPoint web: error preparing server: error opening listener: listen tcp :80: bind: permission denied
Mon utilisateur n'a pas le droit de bind un port privilégié, encore une fois, il est possible de solutionner ce problème de plusieurs façons.
Je décide d'utiliser l'option sysctl
de Docker pour autoriser cela dans mon fichier :
sysctls:
net.ipv4.ip_unprivileged_port_start: 0
Je relance, mais cette fois-ci j'ai une nouvelle erreur :
reverse_proxy_1 | time="2020-07-15T11:40:11Z" level=error msg="Provider connection error Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get \"http://%2Fvar%2Frun%2Fdocker.sock/v1.24/version\": dial unix /var/run/docker.sock: connect: permission denied, retrying in 943.910051ms" providerName=docker
Encore et toujours, plusieurs solutions !
Si tout simplement je n'utilisais pas ma socket Docker sur un service qui est disponible sur internet ? 🤣
Et en plus, j'ai déjà évoqué ce sujet ici :
![](/content/images/2020/05/steve-douglas-JjEbmnY1e0w-unsplash.jpg)
Je vais donc modifier mon fichier docker-compose.yaml
et ma configuration de Traefik pour utiliser ce service :
version: "3.8"
services:
dockerproxy:
image: tecnativa/docker-socket-proxy
environment:
- CONTAINERS=1
networks:
- socket_docker
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
reverse_proxy:
image: traefik:2.2
user: 1001:1001
restart: unless-stopped
sysctls:
net.ipv4.ip_unprivileged_port_start: 0
command:
--api.insecure=true
--log.level=DEBUG
--entrypoints.web.address=:80
--providers.docker.endpoint="tcp://dockerproxy:2375"
--providers.docker.exposedbydefault=false
ports:
# The HTTP port
- "80:80"
# The Admin port
- "8080:8080"
networks:
- socket_docker
- web
networks:
web:
name: traefik_wan
socket_docker:
Pour Traefik, je modifie mon endpoint Docker :
providers.docker.endpoint="tcp://dockerproxy:2375"
Et on relance ! Cette fois-ci, aucune erreur !
Et si on validait le fonctionnement avec un exemple concret ?
Exemple 🚀
Un simple exemple pour valider le bon fonctionnement de la solution avec une instance whoami
.
Je vais donc créer un fichier docker-compose.yaml
:
version: '3.8'
services:
whoami:
image: containous/whoami
labels:
traefik.enable: true
traefik.docker.network: traefik_wan
traefik.http.routers.whoami.entrypoints: web
traefik.http.routers.whoami.rule: 'Host(`whoami.mydomain.com`)'
traefik.http.services.whoami.loadbalancer.server.port: 80
networks:
- traefik_wan
networks:
traefik_wan:
external: true
Il ne reste plus qu'à lancer cette instance :
# docker-compose up -d
et ... :
# curl http://whoami.mydomain.com
Hostname: 3d8322e24526
IP: 127.0.0.1
IP: 172.27.0.3
RemoteAddr: 172.27.0.2:50726
GET / HTTP/1.1
Host: whoami.mydomain.com
User-Agent: curl/7.68.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: X.X.X.X
X-Forwarded-Host: whoami.mydomain.com
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 598416f3d937
X-Real-Ip: X.X.X.X
Et le HTTPS dans tout ça ?!
HTTPS everywhere
Traefik sans HTTPS, c'est comme un repas sans fromage !
![](/content/images/2020/07/traefik-1.png)
Voici donc un fichier docker-compose.yaml
avec l'intégralité de la configuration et une redirection HTTPS :
Créez au préalable le fichier acme.json
et donnez les droits à l'uid 1001 :
touch acme.json
sudo chown 1001:1001 acme.json
Il serait d'ailleurs préférable - surtout dans ce cas précis - de stocker le certificat dans une base de type KV ( cela permet d'éviter la création et les droits à donner au fichier acme.json ) !
Bonus stage 🔥
Voici également une solution équivalente, l'article date un peu, mais globalement la solution reste envisageable :
![](https://miro.medium.com/proxy/1*3sela1OADrJr7dJk_CXaEQ.png)
Enfin, et si il y a encore des sceptiques dans l'utilisation d'un flag comme --user
, je terminerai avec cette citation d'un article de Dan Walsh.
The --user
option is still very necessary and adds a lot of security even when using rootless Podman, and users should still use it to be as secure as possible.
Même avec un container runtime en mode Rootless comme Podman, utilisez cette option est recommandée !
Article complet à retrouver ici :
![](https://www.redhat.com/sysadmin/sites/default/files/styles/full/public/2020-06/container-ship.jpg?itok=MS4y_gtA)
Vous savez maintenant comment exécuter votre instance Traefik avec un utilisateur non privilégié !
Faut-il franchir le pas rapidement ? J'utilise finalement moi-même cette option depuis assez peu de temps car je n'avais pas fait de ce sujet ma priorité. Tout dépendra donc de votre volonté à avancer sur le terrain du DevSecOps ( oui encore un joli mot marketing 😂 ).
En tout cas, les containers de demain devront forcément intégrer ce type de mécanisme de sécurité !
N'hésitez pas à permettre au blog de continuer à exister et à fournir un contenu de qualité - enfin je l'espère - au travers de vos dons sur : buymeacoff.ee/lfache
Et n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter, ou ici 👇