Exposer le socket Docker via TLS

Pour certaines opérations, il peut être utile de pouvoir lancer des commandes Docker “à distance”. Des exemples sont :

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. Pour des raisons de sécurité élémentaires, on l’expose via TLS.

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

Pour ce faire, il faut créer une autorité de certification (CA) côté serveur et générer l’ensemble des éléments ci-dessus. Ces éléments seront créés dans /DATA/docker/remote et appartiendront à root:docker. Pour l’instant, les machines concernées sont :

  • pica01
  • pica02
  • pica01-test

Ces certificats ont une durée de vie qui a été fixée à un an. Il conviendra donc de les renouveler et de les mettre à jour sur les serveurs et les clients concernés tous les ans. 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.

Notons enfin que les passphrases chiffrant les clés privées des autorités de certification, celles-là même qui permettent de produire des certificats serveur et client, sont mises à disposition sur le pass pour les clés autorisées, avec la nomenclature Tech/Keys/<serveur>/ca-key.pem.

L’ensemble des opérations expliquées plus bas, sauf indication contraire, se passent dans le dossier /DATA/docker/remote, sur le serveur concerné (exemple : pica01-test.picasoft.net)

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

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.

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. Attention, prévenez l’équipe technique et choisissez votre moment, puisque tous les conteneurs seront redémarrés. Pour ce faire :

snippet.bash
$ sudo systemctl restart docker
$ systemctl status docker

On vérifie dans les logs que tout s’est bien passé. Un petit docker ps ne fait pas de mal.

Vous remarquerez qu’on ne spécifie nulle part au démon Docker quels certificats il faut utiliser. C’est parce que la configuration du service docker utilise le système de drop-in pour rajouter des paramètres. Par exemple, sur pica01-test, le fichier /etc/systemd/system/docker.service.d/override.conf contient :

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/DATA/docker/remote/ca.pem --tlscert=/DATA/docker/remote/server-cert.pem --tlskey=/DATA/docker/remote/server-key.pem --host=tcp://0.0.0.0:2376 --host=fd://

[Unit]
Requires=docker.socket

On retrouve bien les paramètres attendus.

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
gpg /tmp/certs.tar.gpg
tar -xvf /tmp/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.

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

  • technique/docker/admin/socket-certs.1602683593.txt.gz
  • de qduchemi