Exposer le socket Docker via TLS

Pour certaines opérations, il peut être utile de pouvoir lancer des commandes Docker à distance. Par exemple, les graphes des services sont construits de manière centralisée en récupérant des informations sur les conteneurs qui tournent, à distance. Une chaîne d’intégration aurait besoin de lancer des commandes à distance.

Pour ce faire, l’idée est d’exposer le socket Docker (qui permet d’effectuer toutes les opérations possibles avec Docker) à l’extérieur.

Attention:

Exposer le socket Docker à l’extérieur revient à donner des accès root à quiconque pourra discuter avec lui. Il est donc essentiel d’utiliser TLS pour le sécuriser proprement.

Pour pouvoir communiquer avec le socket Docker depuis l’extérieur, les éléments suivants sont nécessaires :

  • Le certificat d’une autorité de certification
  • Un certificat client
  • Une clé privée client permettant de signer les messages

Note:

La clé privée est l’équivalent d’une clé privée SSH, qui est plus sûre qu’un mot de passe. Le certificat client est une sorte de carte d’identité du client, et ne peut être émis que par l’instance de Docker accessible à l’extérieur.

Pour ce faire, il faut créer une autorité de certification (CA) côté serveur et générer l’ensemble de ces éléments.

Important:

On crée ces éléments dans /DATA/docker/remote, ils appartiendront à root avec le groupe docker, et seront en u=rw,g=r.

Note:

On a fixé arbitrairement la durée de vie des certificats à un an. Il conviendra donc de les renouveler et de les mettre à jour sur les serveurs et les clients concernés tous les ans.

Lien:

Un tutoriel complet est disponible dans la documentation officielle, nous en reprenons les éléments principaux.

Une bonne pratique est de ne pas ré-utiliser les certificats et clés clientes pour des clients multiples : générez autant de certificats que de clients.

C’est la clé la plus importante, puisqu’elle permet de créer les certificats. Si elle est compromise, n’importe qui pourrait créer un certificat client illégitime. C’est pourquoi on la protegera avec un mot de passe.

On génère la clé :

snippet.bash
openssl genrsa -aes256 -out ca-key.pem 4096

Générer la clé avec vaultwarden, avec la nomenclature Tech/Keys/<serveur>/ca-key.pem.

On crée la CA à partir de la clé privée existante, ca-key.pem, à l’aide de la commande suivante :

snippet.bash
$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem

Les informations à remplir sont :

  • Country Name : FR
  • State or Province Name : Picardie
  • Locality Name : Compiegne
  • Organization Name : Picasoft
  • Organization Unit Name : Picasoft
  • Common Name : <serveur>.picasoft.net e.g. pica01-test.picasoft.net
  • Email Address : picasoft@assos.utc.fr

Note:

L’autorité de certification ainsi obtenue permettra de créer des certificats (serveur ou client). Elle n’est jamais utilisée pour chiffrer les communications.

Ce certificat est spécifié lors du lancement du démon Docker, et a plusieurs rôles :

  • Spécifier les hôtes concernés par l’autorité de certification. Ainsi, si ce certificat serveur est utilisé sur un autre hôte, les connexions seront refusées.
  • La clé privée qui lui est associée permet de chiffrer les communications.

On commence par créer une nouvelle clé privée pour le certificat serveur :

snippet.bash
$ openssl genrsa -out server-key.pem 4096

On crée une demande de signature du certificat. Attention, on remplace bien le CN par le champ Common Name de la CA, c’est-à-dire l’URL du serveur.

snippet.bash
$ openssl req -subj "/CN=<serveur>.picasoft.net" -sha256 -new -key server-key.pem -out server.csr

Notez que le fichier extfile.cnf contient déjà toutes les informations nécessaires pour autoriser l’accès au socket Docker. S’il n’existe pas, créez le avec les informations suivantes :

extendedKeyUsage = serverAuth
subjectAltName = DNS:<serveur>.picasoft.net,IP:<IP du serveur>,IP:127.0.0.1

Ainsi, ce certificat pourra être utilisé pour valider les authentifications côté serveur. On génère le certificat signé par la CA elle-même.

snippet.bash
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf

On fait du nettoyage :

snippet.bash
$ rm server.csr

On commence par effacer puis créer à nouveau les dossiers des clients (e.g graphbot, ci) :

Pour chaque client, on crée une nouvelle clé privée:

snippet.bash
$ cd <client>
$ openssl genrsa -out <client>-key.pem 4096

On crée ensuite la demande de signature de certificat :

snippet.bash
$ openssl req -subj '/CN=<client>' -new -key <client>-key.pem -out <client>.csr

Le fichier extfile-client.cnf contient déjà les informations nécessaires pour autoriser l’accès au socket Docker. S’il n’existe pas, créez le avec les informations suivantes :

extendedKeyUsage = clientAuth

Ainsi, le client sera autorisé à s’authentifier. On crée ensuite le certificat client signé.

snippet.bash
$ openssl x509 -req -days 365 -sha256 -in <client>.csr -CA ../ca.pem -CAkey ../ca-key.pem -CAcreateserial -out <client>-cert.pem -extfile ../extfile-client.cnf

On fait du nettoyage :

snippet.bash
$ rm <client>.csr

Renouvelez l’opération pour l’ensemble des clients.

Vérifier que le démon Docker est bien configuré pour démarrer avec TLS et utiliser le certificat généré.

Avant toute chose, il est indispensable de donner des permissions propres à tout ce qui a été généré :

snippet.bash
cd /DATA/docker/remote
sudo chown -R root:docker .
sudo chmod -R u=rwX,g=rwX .

Il faut relancer le démon Docker avec les nouveaux certificats.

snippet.bash
sudo systemctl restart docker
systemctl status docker

Depuis le serveur concerné, récupérez les fichiers suivants :

  • ca.pem : le certificat de la CA elle-même.
  • <client>/<client>-cert.pem : le certificat du client, qui doit avoir été signé par la CA.
  • <client>/<client>-key.pem : la clé privée du client pour chiffrer les communications.

Pour faciliter et sécuriser la tâche, on peut mettre tout ça dans un tar puis le chiffrer avec un mot de passe temporaire.

snippet.bash
tar -cvf certs.tar ca.pem <client>
gpg -c certs.tar
rm certs.tar

Depuis notre PC, on récupère le fichier chiffré, puis on l’envoie vers le bon client:

snippet.bash
rsync <user>@<serveur>.picasoft.net:/DATA/docker/remote/certs.tar.gpg /tmp
rsync /tmp/certs.tar.gpg <user>@<client>.picasoft.net:/tmp

On se connecte au client et on déchiffre et extrait le fichier:

snippet.bash
cd /tmp
gpg certs.tar.gpg
tar -xvf certs.tar

On peut vérifier que tout est ok avec la commande suivante:

snippet.bash
docker --tlsverify --tlscacert=/tmp/ca.pem --tlscert=/tmp/client/<client>-cert.pem --tlskey=/tmp/client/<client>-key.pem -H=<serveur>.picasoft.net:2376 version

Si tout se passe bien, vous obtenez la version de Docker. Vérifiez qu’il n’y a aucune erreur.

Note:

Il est nécessaire de mettre à jour les fichiers des services qui l’utilisent : on se référera à leur documentation.

  • technique/docker/admin/socket-certs.txt
  • de rdelaage