Docker : comment améliorer la sécurité avec un Docker Socket Proxy ?
Sommaire
I. Présentation
Dans un environnement Docker, certains services ont besoin d'un accès au socket de Docker pour fonctionner correctement, notamment pour consulter l'état des conteneurs Docker en cours d'exécution. Dans ce cas, on peut avoir tendance à aller au plus simple : exposer directement le socket Docker. Malheureusement, cette pratique ouvre la porte à un contrôle total du daemon Docker, et donc de l’ensemble de vos conteneurs. Se pose alors une question : comment exposer de manière sécurisée le Socket Docker ?
Dans ce tutoriel, nous allons voir comment utiliser un proxy pour accéder au socket de Docker pour apporter plus de sécurité aux échanges avec l’API Docker. Pour cela, nous allons utiliser l'image d'un projet nommé docker-socket-proxy qui s'appuie sur une image Alpine et HAProxy pour filtrer les accès à l'API Docker.
II. Exposer le socket de Docker : quels sont les risques ?
Le simple fait de monter docker.sock dans un conteneur lui donne un pouvoir total sur le démon Docker. En disant cela, je référence à ce type d'instruction que nous pouvons retrouver dans les fichiers Docker Compose :
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Concrètement, si un attaquant parvient à compromettre ce conteneur, il peut agir sur l’ensemble de votre environnement conteneurisé. Il peut envisager les actions suivantes :
- Supprimer ou modifier n’importe quel conteneur.
- Créer de nouveaux conteneurs (ce qui lui permettrait d'injecter du code malveillant).
- Accéder à l'hôte Docker en lui-même dans le pire des cas.
Monter le socket de Docker en lecture seule ne permet pas de se prémunir contre une attaque potentielle : l'attaquant peut créer un conteneur Docker avec un shell au sein duquel il monte la racine du volume système, qui lui ouvrira la voie royale.
C’est précisément pour éviter ce scénario catastrophe que le proxy pour le socket Docker entre en jeu...
III. Le rôle du Docker Socket Proxy
Le Docker Socket Proxy joue un rôle d’intermédiaire entre vos applications et Docker. Ainsi, au lieu d’exposer directement docker.sock à vos conteneurs (et risquer le pire), ce proxy agit comme un filtre qui ne laisse passer que ce qui est strictement nécessaire et autorisé selon la configuration. C’est lui qui définit quelles actions sont autorisées.
Dans le cas du projet Docker Socket Proxy que nous allons utiliser, le principe est le suivant :
- Par défaut, le proxy autorise les accès aux sections de l'API EVENTS, PING, VERSION.
- Tous les autres accès sont refusés par défaut : CONTAINERS, IMAGES, NETWORKS, VOLUMES, etc...
Vous souhaitez autoriser un accès dans votre proxy ? Vous devez l'autoriser explicitement avec une directive sous la forme suivante : CONTAINERS=1.
Au final, ce proxy est un moyen efficace de concilier praticité et sécurité, sans bouleverser l’architecture existante. Vous n'aurez qu'à effectuer quelques ajustements dans la configuration de vos conteneurs.
Note : la mise en place d'un proxy pour le socket Docker est un très bon moyen de réduire la surface d'attaque de votre système.
IV. Déployer un Docker Socket Proxy
Désormais, nous allons passer au déploiement de Docker Socket Proxy dans un conteneur Docker. Vous devez disposer d'une machine sur laquelle Docker est installé et vous y connecter.
Créez un dossier pour ce projet, puis enchainez sur la création du fichier Docker Compose :
mkdir /opt/docker-compose/docker-socket-proxy
nano /opt/docker-compose/docker-socket-proxy/docker-compose.yml
Voici le code utilisé pour déployer cette application :
services:
docker-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: docker-socket-proxy
restart: unless-stopped
environment:
- LOG_LEVEL=info
- CONTAINERS=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "2375:2375"
networks:
- frontend
networks:
frontend:
external: true
Comme toujours, quelques explications s'imposent :
- Ce conteneur s'appuie sur la dernière image du projet
tecnativa/docker-socket-proxy - Ce conteneur a accès au socket Docker en direct, via la directive sous
volumes:(ce sera le seul à avoir cette ligne désormais). - Le port
2375est exposé, c'est donc sur ce port que devront se connecter les autres conteneurs qui ont besoin de solliciter le proxy. - Le réseau auquel se connecte le conteneur se nomme
frontendet il existe déjà, mais sinon, vous pouvez tout à fait le créer (ici, c'est dans un contexte avec Traefik).
Nous pouvons constater également la ligne précisée ci-dessous, dont l'objectif est de donner accès à l'endpoint /containers/ de l'API. Il y a un lien entre les variables d'environnement supportées par le proxy et les endpoints présents dans l'API de Docker (voir cette documentation).
CONTAINERS=1
Il est important de noter que par défaut, la méthode POST est désactivée ! C'est essentiel d'un point de vue sécurité, car cela signifie que tous les endpoints de l'API sont accessibles en lecture seule. Sinon, un accès à l'endpoint CONTAINERS pourrait, en principe, permettre de créer un conteneur, mais là ce n'est pas le cas.
Vous n'avez plus qu'à lancer la construction du projet :
docker compose up -d
La suite se passe du côté de vos applications.
V. Cas d'usage
A. Utiliser le proxy Socket Docker avec Portainer
Pour commencer, nous allons voir comment configurer Portainer avec le Docker Socket Proxy. Pour rappel, Portainer est une application open source qui facilite l'administration des conteneurs Docker et des éléments associés : volumes, réseaux, images... De ce fait, l'application a besoin d'un ensemble de permissions.
Tout d'abord, dans le fichier Docker Compose de Portainer, vous devez supprimer la liaison avec le socket Docker :
volumes:
- /var/run/docker.sock:/var/run/docker.sock
À la place, ajoutez une commande pour connecter Docker via le proxy (attention au nom du conteneur) :
command: -H tcp://docker-socket-proxy:2375
# ou
command: -H tcp://docker-socket-proxy:2375 --tlsskipverify
Ce qui donne le fichier docker-compose.yml suivant :
services:
portainer:
container_name: portainer
image: portainer/portainer-ce:lts
command: -H tcp://docker-socket-proxy:2375
restart: always
volumes:
- ./portainer_data:/data
ports:
- 9443:9443
networks:
- frontend
networks:
frontend:
external: true
Néanmoins, en l'état, cela ne fonctionnera pas car la configuration de notre proxy est trop restrictive. En effet, nous autorisons uniquement un accès en lecture seule aux conteneurs, ce qui n'est pas suffisant pour Portainer. Il doit pouvoir gérer le réseau, les images, les volumes, etc... Vous devez donc ajuster les permissions de votre proxy pour adopter cette configuration :
environment:
- LOG_LEVEL=info
- CONTAINERS=1
- INFO=1
- TASKS=1
- SERVICES=1
- NETWORKS=1
- VOLUMES=1
- IMAGES=1
- SYSTEM=0
- EXEC=0
- POST=1
L'instruction POST=1 est uniquement utile si vous souhaitez autoriser la création d'éléments via Portainer. Ainsi, nous autorisons les requêtes POST vers les endpoints de l'API qui sont autorisés, donc création / modification de ressources (démarrer/arrêter des conteneurs, créer des services, etc.). Si vous l'utilisez seulement pour de la lecture, ce n'est pas nécessaire d'ajouter cette instruction.
À l'inverse, nous bloquons l'exécution de docker exec via l'API grâce à l'instruction EXEC=0, donc c'est un bon point pour la sécurité. L'idée ici étant de trouver un bon compromis entre sécurité et fonctionnement de Portainer. Nous bloquons aussi les accès sur l'endpoint système avec SYSTEM=0.
Note : rien ne vous empêche de déployer un proxy socket spécifique pour Portainer afin de ne pas ouvrir les permissions pour tous les conteneurs.
Vous pouvez modifier les permissions du proxy à tout moment. Ensuite, en consultant les journaux, vous verrez quelles sont les requêtes autorisées ou refusées en regardant les codes HTTP.
docker logs docker-socket-proxy
# Exemples :
dockerfrontend dockerbackend/dockersocket 0/0/0/2/2 200 9298 - - ---- 2/2/0/0/0 0/0 "GET /v1.51/images/json HTTP/1.1"
dockerfrontend/<NOSRV> 0/-1/-1/-1/0 403 192 - - PR-- 2/2/0/0/0 0/0 "GET /v1.41/info HTTP/1.1"
Portainer pourra ensuite se connecter à votre instance Docker par l'intermédiaire de notre proxy (en mode API).

B. Utiliser le proxy Socket Docker avec Traefik
Cette section explique comment utiliser le proxy Socket Docker avec le reverse proxy Traefik. C'est un très bon exemple, car Traefik est souvent couplé avec Docker et il a un accès aux conteneurs Docker, notamment pour lire les informations sur les labels. Ainsi, un accès en lecture seule sur les conteneurs lui suffit pour fonctionner ! Soit la configuration suivante dans le proxy :
environment:
- LOG_LEVEL=info
- CONTAINERS=1
Dans le fichier Docker Compose de Traefik, il convient donc de supprimer cette ligne :
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Puis, dans le fichier de configurationtraefik.yml, le fournisseur Docker doit être modifié. En principe, vous avez l'instruction endpoint: "unix:///var/run/docker.sock" qui donne un accès direct au socket Docker grâce au volume.
Celle-ci doit être remplacée par une connexion TCP vers le conteneur du proxy sur le port 2375. Ce qui donne :
providers:
docker:
#endpoint: "unix:///var/run/docker.sock"
endpoint: "tcp://docker-socket-proxy:2375"
exposedByDefault: false
Une fois l'opération arrêtée, vous devez relancer la construction du conteneur Traefik avec cette nouvelle configuration. Voilà, votre reverse proxy Traefik fonctionne normalement, et en plus, l'accès aux conteneurs est sécurisé.
VI. Conclusion
L'utilisation d'un proxy pour exposer le Socket de Docker est une bonne manière de filtrer les accès entre vos applications et l'API de Docker. Rien ne vous oblige à le faire, mais c'est un plus pour réduire la surface d'attaque de votre hôte Docker, dans le cas où vous utilisez des applications qui "se connectent" au socket Docker.
FAQ
Pourquoi le socket Docker représente-t-il un risque de sécurité ?
Le socket Docker donne un accès direct au Docker Daemon, c'est-à-dire au moteur Docker en lui-même. Tout conteneur ou application ayant accès à docker.sock sans contrôle peut créer, modifier ou supprimer des conteneurs, accéder aux volumes, manipuler les réseaux et potentiellement compromettre l’hôte en lui-même.
Quel est le rôle du Docker Socket Proxy ?
Le Docker Socket Proxy agit comme un intermédiaire sécurisé (rôle de base du proxy) entre les conteneurs et le démon Docker. Il permet de définir précisément quelles actions sont autorisées (lecture des informations des conteneurs, création de conteneurs, gestion des images, etc.). Cela évite d’exposer le socket brut et réduit considérablement les risques d’escalade : la surface d'attaque est maîtrisée.
Le Docker Socket Proxy fonctionne-t-il avec Docker Swarm ?
Oui. Il peut limiter les permissions liées à Docker Swarm. Cela implique d'ajuster la configuration du proxy pour autoriser les endpoints nécessaires (SERVICES, TASKS, NETWORKS, etc.).
Le Docker Socket Proxy est-il adapté pour un usage en production ?
Oui, à condition de configurer précisément les endpoints autorisés : des tests approfondis peuvent s'avérer nécessaires pour bien identifier les besoins de certaines applications. Le Docker Socket Proxy permet de respecter un principe de moindre privilège, essentiel dans une architecture Docker en production.

