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 :

Sécuriser son daemon Docker
Un nouvel article sur la sécurité de Docker. Dans cet article, je ne traite pas de la sécurisation de votre applicatif conteneurisé mais bien de la sécurité de votre daemon Docker.

Et pour sécuriser votre hôte :

Sécuriser son hôte Docker
L’utilisation de Docker pour conteneuriser vos applications peut vous apporter bien des avantages, notamment en terme de sécurité. Cependant, votre installation de Docker est-elle sécurisée ? Avez-vous déjà vérifier sa sécurité ?
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 ! 😘