Donner accès à votre socket - donc à l'API Docker - à un conteneur, revient globalement à dire que vous donnez un accès root
à votre hôte, voir même à votre cluster Swarm.
Mais certains services ont parfois besoin d'avoir accès aux événements de Docker. Exemple avec Traefik qui va écouter les événements de votre environnement afin de rediriger votre trafic sur les conteneurs.
Il est bien sûr possible d'installer Docker en mode rootless
afin de répondre à cette problématique mais cette installation reste expérimentale, sans parler des limitations.
Et si vous ne souhaitez pas passer par cette installation expérimentale, comment faire ? Comment limiter facilement cet accès à l'API !?
Comment ?
Heureusement pour nous, il existe des solutions afin de limiter l'accès à cette interface !
Nous allons voir aujourd'hui comment protéger votre socket avec l'image Docker : tecnativa/docker-socket-proxy
Que fait donc cette image ?
Celle-ci utilise une image HAProxy:Alpline
avec une configuration permettant de bloquer l'accès à l'API et se configure à l'aide de variables d'environnements.
Cet outil renverra alors une erreur HTTP 403 Forbidden
pour les requêtes qui ne sont pas autorisées.
Passons directement à un exemple avec la mise en place de cet outil, au quel nous ajouterons par la suite un service Traefik.
Docker socket proxy
Si je résume : Traefik n'aura plus un accès direct à la socket, mais devra passer par notre nouvelle image : tecnativa/docker-socket-proxy
qui va autoriser ( ou non ) les appels à l'API.
Grâce à cela, même si notre conteneur Traefik devait être compromis. Il n'offrira pas à l'attaquant un accès root
à notre hôte, au travers de la socket Docker.
Commençons par créer un nouveau service à l'aide de cette image :
services:
dockerproxy:
image: tecnativa/docker-socket-proxy
networks:
- socket_docker
ports:
- 127.0.0.1:2375:2375
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
Enfin je vais le lancer mon nouveau service :
docker-compose up -d
On peut vérifier le fonctionnent en connectant la CLI
Docker sur le service :
export DOCKER_HOST=tcp://localhost
Avec la commande docker version
la configuration ne pose aucun souci :
$ docker version
Client:
Version: 19.03.8
API version: 1.40
Go version: go1.13.8
Git commit: afacb8b7f0
Built: Wed Mar 11 23:42:35 2020
OS/Arch: linux/amd64
Experimental: false
Server:
Engine:
Version: 19.03.8
API version: 1.40 (minimum version 1.12)
Go version: go1.13.8
Git commit: afacb8b7f0
Built: Wed Mar 11 22:48:33 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.3.3-0ubuntu2
GitCommit:
runc:
Version: spec: 1.0.1-dev
GitCommit:
docker-init:
Version: 0.18.0
GitCommit:
Par contre pour les conteneurs en cours d'exécution :
$ docker ps
Error response from daemon: <html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>
C'est normal puisque de base, l'image donne accès sur l'API aux fonctions suivantes :
EVENTS
PING
VERSION
Elle restreint par défaut l'accès à ces fonctions qui sont considérées comme critiques :
AUTH
SECRETS
POST
Soyez vigilant si vous devez autoriser l'accès à ces fonctions.
Enfin toutes les fonctions qui ne sont pas toujours nécessaires et qui sont désactivées par défaut, je ne vais pas toutes les lister ici, on retrouvera principalement :
CONTAINERS
IMAGES
INFO
NETWORKS
NODES
VOLUMES
Pour activer ou désactiver l'accès à certaines fonctionnalités de l'API Docker, vous devez utiliser les variables d'environnements. Exemple pour activer l'accès à la fonction CONTAINERS
:
0
pour révoquer l'accès.1
pour autoriser l'accès.
Je vais donc autoriser ma commande docker
à pouvoir me lister les conteneurs :
services:
dockerproxy:
image: tecnativa/docker-socket-proxy
environment:
- CONTAINERS=1
networks:
- socket_docker
ports:
- 127.0.0.1:2375:2375
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
Si je relance la commande docker ps
:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
32f85e477718 tecnativa/docker-socket-proxy "/docker-entrypoint.…" 21 seconds ago Up 20 seconds 127.0.0.1:2375->2375/tcp dockerfiles_dockerproxy_1
Il ne me reste plus qu'à demander à Traefik d'utiliser cette connexion comme endpoint !
Traefik
Voici mon fichier pour déployer une instance de Traefik :
services:
dockerproxy:
image: tecnativa/docker-socket-proxy
environment:
- CONTAINERS=1
networks:
- socket_docker
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
traefik:
depends_on:
- dockerproxy
image: traefik:2.2
restart: unless-stopped
networks:
- socket_docker
- traefik
ports:
- 80:80
volumes:
- ./traefik.yaml:/traefik.yaml
Finalement ce déploiement ressemble beaucoup aux autres déploiements que j'ai déjà pu proposer sur ce blog.
À la différence cette fois, que j'ai deux réseaux déclarés :
- Celui pour me connecter à mon service "docker-socket-proxy",
- Mon réseau qui permet la communication entre les conteneurs qui diffusent un service Web et Traefik.
Autre différence, je ne monte aucun volume pour donner accès à ma socket Docker ! Le reste de la configuration va se trouver dans mon fichier traefik.yaml
:
providers:
docker:
endpoint: "tcp://dockerproxy:2375"
Dorénavant Traefik va utiliser notre proxy pour écouter les événements et non plus directement la socket.
Vous savez maintenant comment protéger votre socket Docker en limitant l'accès à l'API aux méthodes que vous aurez préalablement autorisées.
Nous avons également pu voir comment mettre en place cette protection avec Traefik. Grâce à cette protection et les éléments de sécurisation que nous avons pu aborder par le passé, votre hôte Docker commence à être correctement protégé !
Pour rappel, voici deux articles pour protéger votre daemon Docker :
Et pour sécuriser votre hôte :
En tout cas n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter ! C'est toujours un plaisir d'avoir des retours ! 😘