Déployer, manipuler et sécuriser un serveur Registry Docker privé

Ce chapitre, vous explique l'utilité d'un registre Docker et vous apprend à déployer, manipuler et sécuriser un serveur Registry Docker privé.

Introduction

Dans ce chapitre, nous allons nous intéresser à la partie Registry (en fr : Registre) dans Docker.

C'est quoi ?

Le Registry Docker est un système de stockage et de distribution d’image Docker open-source (sous la licence Apache), déployé côté serveur. Il permet aux utilisateurs d'extraire et insérer des images Docker dans un dépôt avec les autorisations d'accès appropriées. La même image peut avoir plusieurs versions différentes, identifiées par leurs tags.

Quand l'utiliser ?

Vous devez utiliser le Registry si vous voulez :

  • Contrôler étroitement l'endroit où vos images sont stockées
  • Posséder pleinement le contrôle de votre pipeline de distribution d'images
  • Intégrer étroitement le stockage et la distribution des images dans votre flux de travail de développement interne

Déploiement et manipulation d'un Registry privé

Création du Registry privé

Par défaut, le moteur Docker interagit avec le DockerHub. DockerHub est le registre officiel de Docker offrant une solution prête à l'emploi, que nous avons déjà eu l’occasion d'utiliser ici . Il ne nécessite aucune maintenance et fournit un Registry gratuit, ainsi que des fonctionnalités supplémentaires telles que des comptes d'organisation, une intégration à des solutions de contrôle de source notamment Github et Bitbucket, etc. Mais dans ce cours, nous allons nous intéresser au déploiement d'un serveur Docker Registry privé.

Avant de pouvoir déployer un Registry, vous devez d'abord installer le moteur Docker sur la machine hôte qui hébergera vos images Docker, car un Registry n’est rien d’autre qu’une image Docker qui attend à être exécutée. Voici la commande qui permet de créer un Docker Registry privé :

docker run -d -p 5000:5000 --restart=always --name mon-registry registry:2

Déposer une image dans le Registry privé

Premièrement, nous allons créer une image personnalisée (alpine + git) que nous allons par la suite déposer dans notre Registry. Commencez d'abord par créer un Dockerfile et ajoutez dedans le contenu suivant :

FROM alpine:latest
RUN apk add --no-cache git

Construisez ensuite votre image :

docker build -t alpinegit .

En vue de pousser notre image dans notre Registry, il faut au préalable créer un nouveau tag de votre image en respectant le format suivant :

docker tag alpinegit <SERVER NAME REGISTRY>:<PORT SERVER REGISTRY>/<CONTAINER NAME>

Soit :

docker tag alpinegit localhost:5000/alpinegit

Dès à présent, vous pouvez envoyer votre image vers le registry docker privé :

docker push localhost:5000/alpinegit

Visualiser les images disponibles dans le Registry privé

Une fois votre image envoyée, il est possible de la visualiser grâce à l'API du Docker Registry. Je vais utiliser la commande curl pour manipuler l'API du Registry.

Voici l'url qui permet de lister les différentes images dans votre Registry Docker :

curl -X GET http://localhost:5000/v2/_catalog

Résultat :

{"repositories":["alpinegit"]}

Nous allons créer une nouvelle image, qu'on pushera ensuite dans notre registry privé, dans le but de savoir si l'API Docker Registry prend bien en compte notre nouvelle image :

Dockerfile

FROM alpine:latest
RUN apk add --no-cache mysql-client
ENTRYPOINT ["mysql"]

Vous connaissez maintenant la musique 🎵, en construit l'image, change son tag et on la push :

$ docker build -t alpinemysql .
$ docker tag alpinemysql localhost:5000/alpinemysql
$ docker push localhost:5000/alpinemysql

Vérifions ensuite si l'api nous retourne bien notre nouvelle image :

curl -X GET http://localhost:5000/v2/_catalog

Et c'est bien le cas :

{"repositories":["alpinegit","alpinemysql"]}

Si vous voulez, vous pouvez supprimer vos images localement à l'aide de la commande docker rmi et vous verrez que vos images seront toujours disponibles dans votre docker resigtry, car ces dernières sont stockées dans votre conteneur registry. Par la suite vous pouvez récupérer vos images depuis votre registry privé avec la commande suivante :

docker pull localhost:5000/alpinegit
docker pull localhost:5000/alpinemysql

Visualiser les différents tags d'une image dans le Registry privé

Dans cette partie, nous allons rajouter un nouveau tag nommé "test" sur notre image alpinegit,

docker tag alpinegit localhost:5000/alpinegit:test
docker push localhost:5000/alpinegit:test

Nous allons maintenant, réutiliser l'api Docker Registry pour lister les différents tags de notre image alpinegit :

curl -X GET http://localhost:5000/v2/alpinegit/tags/list

Résultat :

{"name":"alpinegit","tags":["test","latest"]}

Sécurité

Le stockage dans un Registry Docker

Par défaut aucun volume n'est créé, donc si vous quitter votre conteneur registry, vous perdrez automatiquement toutes vos images hébergées dans votre registry. Il est donc très important de rajouter un volume. L'exemple suivant monte le répertoire hôte data/ vers le dossier /var/lib/registry/ du conteneur mon-registry.

Premièrement, on crée le dossier de stockage sur notre machine hôte :

mkdir data

Deuxièmement, on démarre notre conteneur avec le volume adéquat :

docker run -d \
    -p 5000:5000 \
    --restart=always \
    --name mon-registry \
    -v "$(pwd)"/data:/var/lib/registry \
    registry:2

le chiffrement

L'exécution d'un Registry accessible uniquement en local a une utilité limitée. Afin de rendre un Registry accessible aux hôtes externes, vous devez d'abord sécuriser le Registry Docker à l'aide de TLS.

Bien qu'il soit vivement recommandé de sécuriser votre base de registre à l'aide d'un certificat TLS émis par une autorité de certification connue, vous pouvez choisir d'utiliser des certificats auto-signé ou d'utiliser votre base de registre via une connexion HTTP non chiffrée. L'un ou l'autre de ces choix implique des compromis en matière de sécurité et des étapes de configuration supplémentaires.

Information

Le Registry prend en charge l'utilisation de Let's Encrypt afin d'obtenir automatiquement un certificat approuvé par les navigateurs.

Cependant dans notre exemple, nous allons générer des certificats auto-signés, ils ne seront pas reconnus par nos navigateurs mais au moins la communication avec notre Registry sera chiffrée.

Conseil

En utilisant un registre en HTTP seulement, vous exposez alors votre base de registre à des attaques triviales de type "homme au milieu" (MITM).

En premier lieu, commencez par créer un dossier nommé certs pour stocker nos certificats :

mkdir certs

En seconde partie, nous allons générer nos propres certificats avec l'outil openssl :

openssl req \
    -newkey rsa:4096 -nodes -sha256 -keyout certs/localhost.key \
    -x509 -days 365 -out certs/localhost.crt

Openssl vous demandera quelques informations, vous pouvez laisser les options par défaut en appuyant sur la touche entrée.

Pour utiliser les certificats générés, nous allons alors surcharger des variables d'environnements proposé par l'image registry:2 et utiliser le port 443 :

docker run -d \           
    --restart=always \   
    --name mon-registry \
    -v "$(pwd)"/data:/var/lib/registry \
    -v "$(pwd)"/certs:/certs \         
    -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/localhost.crt \
    -e REGISTRY_HTTP_TLS_KEY=/certs/localhost.key \
    -p 443:443 \
    registry:2

Information

La ligne REGISTRY_HTTP_ADDR=0.0.0.0:443, permet de rendre notre registry accessible depuis n'importe IP.

Maintenant on est capable d'envoyer nos images vers notre nouveau registry chiffrée :

$ docker tag alpinegit localhost:443/alpinegit
$ docker tag alpinemysql localhost:443/alpinemysql
$ docker push localhost:443/alpinegit
$ docker push localhost:443/alpinemysql

La prochaine étape consiste à visiter la page suivante https://localhost/v2/_catalog. Si votre navigateur vous le permet, alors il vous demandera de rajouter le certificat en question car ce dernier n'est pas reconnu par une CA. Dans mon cas j'utilise Firefox, et voici ce que j'obtiens comme résultat :

Ajout du certificat TLS du Docker registry privé depuis la navigateur Firefox

Cliquez ensuite sur le bouton "Avancé", puis sur "Accepter le risque et poursuivre" et si tout se passe comme prévu alors vous obtiendrez le résultat suivant :

Visite du catalogue Docker registry privé avec du HTTPS depuis la navigateur Firefox

Restriction d'accès

Pour rajouter une autre couche de sécurité dans le fonctionnement de vos registres sur des réseaux locaux sécurisés, vous pouvez implémenter des restrictions d'accès.

Pour le moment, nous allons mettre en place une Authentification de base avec un utilisateur et un mot de passe globaux à l'aide du fichier htpasswd.

Heureusement que notre baleine (mascotte de docker 😋) a pensé à nous, car depuis l'image registry, il est possible de générer le fichier htpasswd.

On commence d'abord par créer un dossier auth pour stocker notre fichier :

mkdir auth

Par la suite on génére notre fichier htpasswd :

docker run --rm \
    --entrypoint htpasswd \
    registry:2 -Bbn testuser testpassword > auth/htpasswd

Démarrer ensuite votre conteneur en créant un volume qui prend en compte le fichier htpasswd :

docker run -d \
    -p 5000:5000 \
    --restart=always \
    --name mon-registry \
    -v "$(pwd)"/data:/var/lib/registry \
    -v "$(pwd)"/auth:/auth \
    -e "REGISTRY_AUTH=htpasswd" \
    -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
    -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
    -v "$(pwd)"/certs:/certs \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/localhost.crt \
    -e REGISTRY_HTTP_TLS_KEY=/certs/localhost.key \
    registry:2

Maintenant, il faut se connecter sur votre Docker Registry privé :

docker login localhost:5000
Username: testuser
Password: testpassword

Si tout se déroule comme prévu, vous devriez avoir le message suivant :

Login Succeeded

Une fois authentifié, vous pouvez alors envoyer votre image dans votre Registry privé :

docker push localhost:5000/alpinemysql

Docker Compose

Normalement, si vous avez suivi ce chapitre depuis le début, vous devez vous retrouver avec l'arborescence suivante :

├── auth
│   └── htpasswd
├── certs
│   ├── localhost.crt
│   └── localhost.key
└──  data

Reprenons les fonctionnalités que nous venons de mettre en place. Actuellement nous avons :

  • Un volume pour stocker nos images envoyées par l'utilisateur
  • Une communication chiffrée
  • Un système d'authentification basique

Nous allons reprendre toutes ces fonctionnalités et les rajouter dans un Docker Compose :

version: '3.7'
services:
    registry:
        restart: always
        image: registry:2
        container_name: my-web-container
        ports:
            - 5000:5000
        environment:
            REGISTRY_HTTP_TLS_CERTIFICATE: /certs/localhost.crt
            REGISTRY_HTTP_TLS_KEY: /certs/localhost.key
            REGISTRY_AUTH: htpasswd
            REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
            REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
        volumes:
            - ./data/:/var/lib/registry
            - ./certs/:/certs
            - ./auth/:/auth

Conclusion

Nous avons réussi à mettre en place un Registry Dokcer privé en rajoutant quelques couches de sécurité. Il est possible bien sûr de perfectionner encore plus ce registry basique en prenant par exemple en charge l’envoi de notifications Webhook en réponse aux événements se produisant dans le registre , ou en rajoutant des autorisations d'accés comme le readonly pour certains clients qui ne seront pas autorisés à écrire dans le registre etc ... Je vous conseille donc vivement de jeter un coup d'œil à la documentation officielle des Registry Docker.

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

25 commentaires

ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Merci pour la mise à jour @tubie2b

22/07/2019 à 02:00
barry
Membre
Avatar de barry
barry
Membre

Bonjour Hatim, tout d'abord je tenais à te remercier, ton blog est vraiment génial !

Je rencontre un souci dans la partie chiffrement, la commande

docker run -d \                --restart=always \        --name mon-registry \     -v "$(pwd)"/data:/var/lib/registry \     -v "$(pwd)"/certs:/certs \              -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \     -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/localhost.crt \     -e REGISTRY_HTTP_TLS_KEY=/certs/localhost.key \     -p 443:443 \     registry:2

me retourne cela : 

bash: --name : commande introuvable

bash: -e : commande introuvable

Saurais tu d'où pourrait venir le problème ?

Pour info la commande :

docker run -d \     -p 5000:5000 \     --restart=always \     --name mon-registry \     -v "$(pwd)"/data:/var/lib/registry \     registry:2

ne me retourne aucune erreur.

Merci d'avance pour tes compléments d'information.

20/07/2019 à 20:34
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Bonjour @siaskine , tu as plusieurs façons de faire cela.Par exemple, si tu crées ton registry sur une instance qui possède déjà une IP publique tu peux créer un driver host à utiliser sur ton conteneur (lien de l'article:  https://devopssec.fr/article/fonctionnement-manipulation-reseau-docker ). Si ton registry est privé et qu'il est sur ton pc perso, tu peux faire du NAT/PAT.

19/07/2019 à 16:17
siaskine
Membre
Avatar de siaskine
siaskine
Membre

Bonjour, merci pour l'article. Mais j'ai une question, comment faire pour mettre ma registry sur une adresse ip afin qu'un client puisse y avoir accès !? 

17/07/2019 à 23:07
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Content que ça t'aide. Pense bien à monitorer la taille de data/ pour pas saturer ton disque.

16/07/2019 à 18:18
barbier-xavier
Membre Actif Rédacteur
Avatar de barbier-xavier
barbier-xavier
Membre Actif Rédacteur

Top le tuto, merci. Ça marche nickel en local avec le volume data/.

16/07/2019 à 11:38
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

T'as bien fait un docker pull registry:2 avant de lancer la commande ?

16/07/2019 à 03:47
elodie52
Membre Actif
Avatar de elodie52
elodie52
Membre Actif

J'ai une erreur lors de la génération du htpasswd, la commande échoue avec registry:2 not found.

15/07/2019 à 21:03
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

@Dexter38 Merci !

15/07/2019 à 19:28
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, sur le client qui fait le docker pull, modifie le daemon.json :

{
  "insecure-registries" : ["ton-ip-registry:443"]
}

Puis systemctl restart docker.

15/07/2019 à 15:27
bbarthelemy
Membre Actif
Avatar de bbarthelemy
bbarthelemy
Membre Actif

Le certificat auto-signé me fait galérer avec les clients. Je peux passer outre la vérification TLS sur un client spécifique ?

15/07/2019 à 09:04
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est un peu sale, mais tu dois supprimer les fichiers directement dans le volume data/ et lancer le garbage collector :

docker exec mon-registry bin/registry garbage-collect /etc/docker/registry/config.yml

À ne jamais faire en prod sans backup.

15/07/2019 à 03:50
eric-pichon
Membre Actif
Avatar de eric-pichon
eric-pichon
Membre Actif

Comment on fait pour supprimer une image du registry une fois qu'elle est poussée ?

14/07/2019 à 20:22
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Ah, bien vu. Si tu veux le 443, il faut mapper les ports dans le compose :

ports:
  - 443:443

Et surtout ajouter l'env REGISTRY_HTTP_ADDR: 0.0.0.0:443.

14/07/2019 à 14:35
grondin-luc
Membre Actif
Avatar de grondin-luc
grondin-luc
Membre Actif

Le docker-compose.yml proposé ne lance pas le conteneur en 443 comme dans l'exemple TLS. Il reste sur le 5000.

14/07/2019 à 08:02
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, tu peux monter n'importe quel chemin hôte vers /var/lib/registry. Exemple : -v /mnt/mon_disque_externe:/var/lib/registry.

14/07/2019 à 03:00
fleury-emmanuelle
Membre Actif
Avatar de fleury-emmanuelle
fleury-emmanuelle
Membre Actif

Est-ce qu'on peut changer le dossier de stockage par défaut /var/lib/registry ? Je veux monter un disque externe.

13/07/2019 à 19:26
Dexter38
Membre
Avatar de Dexter38
Dexter38
Membre

Une fois authentifier... authentifié 😉

13/07/2019 à 13:22
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie le mapping du volume dans ton docker run. Si ton chemin absolu est foireux, le conteneur ne voit pas le fichier.

Assure-toi que -v "$(pwd)"/auth:/auth pointe bien vers le dossier qui contient ton fichier généré.

13/07/2019 à 12:00
hebert-camille
Membre Actif
Avatar de hebert-camille
hebert-camille
Membre Actif

J'ai suivi la partie sécurité avec le htpasswd, mais ma commande docker login me renvoie 401 Unauthorized systématiquement. Le fichier est bien dans auth/htpasswd.

13/07/2019 à 06:27
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

T'as bien configuré le daemon Docker pour accepter les registres non sécurisés si tu n'as pas encore mis en place le TLS ?

Ajoute --insecure-registry localhost:5000 dans ton fichier /etc/docker/daemon.json et redémarre le daemon.

13/07/2019 à 02:05
anastasie-navarro
Membre Actif
Avatar de anastasie-navarro
anastasie-navarro
Membre Actif

Quand je push mon image avec docker push localhost:5000/alpinegit, j'ai une erreur connection refused. Pourtant le conteneur est up.

12/07/2019 à 20:07
veronique28
Membre Actif
Avatar de veronique28
veronique28
Membre Actif

Bien vu, c'était un vieux processus qui traînait. C'est reparti.

12/07/2019 à 13:32
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie tes logs avec docker logs mon-registry. Souvent, c'est un conflit de port si un service utilise déjà le 5000.

12/07/2019 à 07:19
alexandrie91
Membre Actif
Avatar de alexandrie91
alexandrie91
Membre Actif

J'ai testé la commande docker run -d -p 5000:5000 --restart=always --name mon-registry registry:2 mais le conteneur ne démarre pas sur ma Debian. Une idée?

12/07/2019 à 00:28

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire