
Beaucoup d'outils réclament /var/run/docker.sock : superviseurs, reverse-proxies à découverte automatique, dashboards, runners de CI. Or donner le socket, c'est donner un accès root à l'hôte. Le réflexe courant, monter le socket en :ro, ne protège pas : le :ro porte sur le fichier, pas sur l'API, et les écritures passent quand même. La vraie solution est un docker-socket-proxy qui filtre l'API et n'autorise que la lecture.
Ce guide déploie ce proxy, y branche un outil via DOCKER_HOST, prouve que l'écriture est bloquée, puis montre comment n'ouvrir que le strict nécessaire. Public : intermédiaire à avancé. Les commandes sont testées.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre pourquoi
docker.sock:ron'empêche pas les écritures. - Déployer un docker-socket-proxy en lecture seule.
- Brancher un outil dessus via
DOCKER_HOST. - Restreindre l'accès aux endpoints strictement nécessaires.
Prérequis
Section intitulée « Prérequis »- Docker et Docker Compose.
- Un outil qui a besoin du socket (monitoring, Traefik, dashboard).
- Des bases sur le modèle de menaces Docker.
Pourquoi :ro ne suffit pas
Section intitulée « Pourquoi :ro ne suffit pas »Le socket Docker est un canal de communication avec le démon, qui tourne en root. On s'y connecte et on échange des requêtes HTTP : un POST /containers/{id}/stop emprunte le même canal qu'un GET /containers/json. Le flag :ro empêche seulement de remplacer le fichier socket, il n'inspecte pas le contenu des requêtes.
La preuve : montons le socket en :ro et tentons une écriture.
docker run -d --name cible nginx:alpineCID=$(docker inspect -f '{{.Id}}' cible)
# POST d'arrêt, à travers un socket monté en :rodocker run --rm --user 0 -v /var/run/docker.sock:/var/run/docker.sock:ro \ curlimages/curl -s -o /dev/null -w "%{http_code}\n" \ -X POST --unix-socket /var/run/docker.sock "http://localhost/v1.44/containers/$CID/stop"# 204
docker ps -a --format '{{.Names}} {{.Status}}' | grep cible# cible Exited (0)HTTP 204, conteneur arrêté à travers un socket monté en read-only. Le :ro n'a rien empêché : tout outil ayant le socket monté a le pouvoir d'agir, :ro ou non.
Il faut donc un composant qui inspecte l'API et refuse les endpoints d'écriture. C'est exactement le rôle du proxy.
Déployer un docker-socket-proxy
Section intitulée « Déployer un docker-socket-proxy »Le proxy le plus répandu est tecnativa/docker-socket-proxy : il se place devant le socket, expose un endpoint TCP, active uniquement les groupes d'endpoints que vous déclarez, et refuse tout POST par défaut.
-
Déclarer le proxy en n'activant que les groupes de lecture utiles. Ici de quoi alimenter un outil de monitoring ou une découverte de conteneurs.
services:socketproxy:image: tecnativa/docker-socket-proxy:latestenvironment:- CONTAINERS=1- INFO=1- VERSION=1- EVENTS=1- IMAGES=1- NETWORKS=1- PING=1# POST non défini => 0 => toute écriture renvoie 403volumes:- /var/run/docker.sock:/var/run/docker.sock:ronetworks: [proxynet]restart: unless-stoppednetworks: { proxynet: {} } -
Démarrer et vérifier que la lecture répond.
Fenêtre de terminal docker compose up -d socketproxydocker compose exec socketproxy wget -qO- http://localhost:2375/version | head -c 80# {"Platform":{"Name":"Docker Engine..."} -> lecture OK
Brancher un outil via DOCKER_HOST
Section intitulée « Brancher un outil via DOCKER_HOST »L'outil ne monte plus le socket : il pointe sur le proxy via DOCKER_HOST. La plupart des outils Docker (SDK Go/Python) honorent cette variable.
monitoring: image: votre/outil:latest environment: - DOCKER_HOST=tcp://socketproxy:2375 # jamais le vrai socket networks: [proxynet] depends_on: [socketproxy]L'outil découvre alors les conteneurs à travers le proxy, sans jamais accéder au socket réel. J'ai vérifié avec un superviseur : l'auto-découverte de la stack fonctionne à l'identique.
Vérifier l'isolation
Section intitulée « Vérifier l'isolation »Le test qui compte : la lecture passe, l'écriture est refusée.
# Lecture : autoriséedocker compose exec socketproxy wget -qO- -S http://localhost:2375/version 2>&1 | grep HTTP# HTTP/1.1 200 OK
# Écriture : refuséeCID=$(docker inspect -f '{{.Id}}' un-conteneur)docker compose exec socketproxy \ wget -qO- -S --post-data='' "http://localhost:2375/containers/$CID/stop" 2>&1 | grep HTTP# HTTP/1.1 403 Forbidden200 en lecture, 403 en écriture. Le read-only n'est plus une promesse de l'outil, c'est une règle imposée par le proxy. Même si l'outil était compromis, il ne pourrait pas agir sur vos conteneurs.
N'ouvrir que le strict nécessaire
Section intitulée « N'ouvrir que le strict nécessaire »Chaque variable active un groupe d'endpoints. Le principe de moindre privilège impose de n'activer que ce dont l'outil a réellement besoin.
| Variable | Ouvre | Pour quel usage |
|---|---|---|
CONTAINERS | liste et inspection des conteneurs | monitoring, découverte |
IMAGES | liste des images | détection de mises à jour |
NETWORKS | réseaux | découverte (Traefik) |
EVENTS | flux d'évènements | réaction temps réel |
INFO, VERSION, PING | métadonnées et santé | base |
Sécuriser le proxy lui-même
Section intitulée « Sécuriser le proxy lui-même »Le proxy, lui, monte le vrai socket : c'est le composant de confiance, root-equivalent. Il faut donc le traiter comme tel.
- Réseau dédié : placez le proxy et ses clients sur un réseau interne (
proxynet), sans port publié sur l'hôte. Le2375ne doit jamais être exposé au réseau. - Durcissement :
read_only: true,security_opt: [no-new-privileges:true],cap_drop: [ALL]sur le conteneur proxy. - Un proxy par besoin : plutôt qu'un proxy tout-ouvert partagé, un proxy restreint par outil limite le rayon d'impact.
Alternatives
Section intitulée « Alternatives »- Proxy durci :
wollomatic/socket-proxyva plus loin (filtrage par regex des chemins, TLS mutuel, allowlist stricte). À considérer pour un usage sensible. - Mode rootless : le Docker rootless déplace le démon hors de root, ce qui réduit l'enjeu à la source. Complémentaire du proxy.
Dépannage
Section intitulée « Dépannage »- L'outil ne voit rien : le groupe d'endpoints nécessaire n'est pas activé (souvent
CONTAINERS=1manquant), ouDOCKER_HOSTne pointe pas sur le proxy. - 403 sur une action légitime : l'outil tente une écriture ; décidez si vous l'autorisez via un
ALLOW_*précis, ou si l'action n'a rien à faire là. DOCKER_HOSTignoré : certains outils utilisent un chemin de socket codé en dur ; vérifiez leur doc, beaucoup acceptent untcp://ou unDOCKER_GID.- Le proxy ne démarre pas : il lui faut le vrai socket monté (
/var/run/docker.sock:ro) et le réseau partagé avec ses clients.
À retenir
Section intitulée « À retenir »docker.sock:ron'empêche aucune écriture : le:roporte sur le fichier, pas sur l'API.- Un docker-socket-proxy filtre l'API :
GETautorisés,POSTen 403. - Les outils se branchent via
DOCKER_HOST=tcp://socketproxy:2375, sans le vrai socket. - N'activez que les groupes d'endpoints nécessaires ;
POST/ALLOW_*rouvrent l'écriture. - Le proxy est le composant de confiance : réseau interne, pas de port publié, conteneur durci.