Activer l'API Docker (+nginx)🐱‍👤

Alors, dans certaines situations, vous pouvez avoir besoin de contrôler Docker en dehors de votre environnement d'accueil (par exemple si vous utilisez un outil comme Portainer).

Supposons que vous vous déployez sur un de vos serveurs et que pour le déploiement à distance, vous ayez besoin d'une connexion à votre Docker Daemon. Dans ce scénario, la connexion à l'API REST de Docker vient à votre secours, mais avec un léger coût de sécurité.

Mais d'abord, activons-la. En supposant que vous avez déjà un serveur sur lequel Docker est installé (proprement 😉).

Pour activer l'API REST 🐱‍🏍

Vous devez éditer le fichier suivant:

sudo vi /lib/systemd/system/docker.service

Et y remplacer la ligne commençant par ExecStart par ça:

ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 --containerd=/run/containerd/containerd.sock

De cette façon, le serveur du moteur Docker sera lié à la socket Unix et au port TCP 2376, qui est le port par défaut de Docker, il est fortement conseillé, par mesure de sécurité de le changer.

Il en est de même pour l'adresse IP d'écoute, remplacez 0.0.0.0 par l'interface souhaitée. Par défaut, l'API écoute ainsi sur toutes les interfaces, ce qui n'est pas très security-friendly... 😒

Ainsi, de manière "sécurisée":

ExecStart=/usr/bin/dockerd -H fd:// -H tcp://127.0.0.1:4243 --containerd=/run/containerd/containerd.sock

Vous devez juste vous assurer qu'il n'est pas utilisé par un autre service sur votre machine.Une fois que c'est fait, il va falloir recharger le bousin et redémarrer Docker. 😎

sudo systemctl daemon-reload
sudo systemctl restart docker

Tester l'API 🤷‍♂️

Bon, même si "tester c'est douter", ça reste quand même pas une mauvaise idée que de s'assurer que ça fonctionne bien.

Because...

Pour ce faire, depuis la machine en local, on va utiliser la commande suivante pour récupérer les images disponibles:

curl -sX GET http://localhost:<PORT CUSTOM>/images/json

On pourra utiliser la même commande depuis une machine distante tout en replaçant localhost par l'IP en écoute (en s'assurant que les flux soient bien ouverts 😜).

Sécuriser tout ça ! 😒

Effectivement, c'est bien beau tout ça, ça marche, tout le monde il est content mais c'est tout sauf sécurisé...😢

En premier lieu, ce que l'on va faire, c'est mettre en place en front, un reverse proxy nginx.

Au niveau de celui-ci on va utiliser une fonctionnalité qui se nomme "HTTP Basic Authentication". Ce n'est pas une fonctionnalité qui est propre à nginx (puisque c'est un standard HTTP) mais j'ai mes habitudes avec celui-ci!

On va donc commencer par créer un fichier avec le mot de passe hashé:

sudo htpasswd -c /etc/nginx/.htpasswd <NOM D'UTILISATEUR>
Stockez-le bien le mot de passe, vous devrez l'utiliser à chaque authentification. N'oubliez pas d'installer le paquet apache2-utils.

Afin d'accéder à l'API du Docker fonctionnant sur la socket Unix en local, le processus nginx doit avoir les privilèges root. C'est pourquoi il faut éditer le fichier /etc/nginx/nginx.conf en changeant la configuration de l'utilisateur en utilisateur root.

La commande suivante le fait pour vous (si ce n'est pas beau ça...):

sudo sed -i 's/user .*;/user root;/' /etc/nginx/nginx.conf

Nous devons maintenant exposer le port 4243 comme proxy pour le socket Unix fonctionnant localement avec le HTTP Basic Authentication. 😁

La commande suivante va créer le site nginx pour vous :

sudo tee /etc/nginx/sites-available/docker <<EOF 
upstream docker {
  server unix:/var/run/docker.sock;
}

server {
  listen 4242 default_server;
  location / {
    proxy_pass http://docker;
    
    # Faire son videur de boîte de nuit...
    allow <IP DU SERVEUR ATTAQUANT L'API>;
    deny all;
    
    auth_basic_user_file /etc/nginx/.htpasswd;
    auth_basic "Access restricted";
    
    # Journaliser tout ça...
    error_log /var/log/docker/docker-api-error.log;
    access_log /var/log/docker/docker-api-access.log;
  }
}
EOF
Dans notre cas, seul le port 4242/tcp sera visible depuis l'extérieur (pour le moment). Pour ce qui est du format des journaux, vous trouverez plus d'informations ici.

Il faut créer les fichiers de journalisation:

sudo mkdir -p /var/log/docker/
sudo touch /var/log/docker/docker-api-error.log
sudo touch /var/log/docker/docker-api-access.log

Ne reste plus qu'à l'activer:

sudo ln -s /etc/nginx/sites-available/docker /etc/nginx/sites-enabled/

Encore une fois "tester c'est douter" mais quand même... dans le doute.😜

sudo nginx -t && nginx -s reload

Je vous conseil fortement d'utiliser un certificat SSL/TLS, je ne vais pas vous détailler comment en générer un auto-signé (ou via une PKI) mais ce n'est qu'un conseil d'ami.

Sinon vous pouvez suivre ce doc.

Maintenant, on va filtrer! 😊

Je ne veux que que ma machine distante puisse accéder à l'API, pour ça, on va filtrer avec le firewall:

sudo ufw deny 4242/tcp
sudo ufw deny 4243/tcp
sudo ufw allow from <ADRESSE IP> to any port 4242

Et on test une dernière fois pour s'assurer que ça fonctionne bien!

curl -sX GET http://username:password@localhost:4242/images/json

Ou encore:

curl -sX GET http://username:password@mon-petit-fqdn.local:4242/images/json

Voilà tout! Bien évidemment, il y a plein d'autres astuces. 😜