Logo Clair

Cet article est un guide pour utiliser le scanner de vulnérabilité clair avec GitLab.

Public visé: Administrateurs systèmes, DevOps familiers avec l’écosystème docker.

Par Quentin Anglade, bidouilleur professionnel et expert sécurité @ Objectif Libre

Analysez des images docker avec gitlab et clair

Vous avez une intégration continue avec Gitlab dont vous êtes fier, qui build et teste vos logiciels. Ceux-ci arrivent en production dans des conteneurs docker. Ou vous n’en avez pas (encore), mais vous êtes curieux ? Quelle que soit votre situation, pourquoi ne pas utiliser le registry docker intégré à Gitlab et intégrer un scan de vulnérabilités dans votre chaîne d’intégration continue ?

Vous pourriez utiliser Gitlab enterprise edition (Gitlab EE), mais vous préférez héberger vous-même et/ou utiliser des logiciels open-source ? On vous explique comment faire !

Ce dont vous avez besoin

Vous aurez besoin d’un gitlab, de préférence avec une IP publique et un domaine pour avoir facilement des certificats HTTPS avec Let’s encrypt. Je ne peux que conseiller de mettre en place un Gitlab de test avant de toucher à un Gitlab en production. Je vais dans cet article utiliser le domaine gitlabtest.objectif-libre.com. Voici un exemple pour déployer facilement un Gitlab :

sudo docker run --detach \
--hostname gitlabtest.objectif-libre.com \
--publish 443:443 --publish 80:80 --publish 2222:22 --publish 4443:4443\
--name gitlab \
--restart always \
--volume /data/gitlab/config:/etc/gitlab \
--volume /data/gitlab/logs:/var/log/gitlab \
--volume /data/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest

Cela va déployer l’image docker officielle de Gitlab, avec les spécificités suivantes :

  • Stockage : toutes les données sont stockées dans le dossier /data/gitlab, vous pouvez l’ajuster à vos besoins.
  • Réseau : les ports 80 et 443 sont exposés pour l’http(s), le port 2222 est dirigé sur le port 22 du conteneur pour éviter les conflits avec le serveur SSH de l’hôte. Gitlab ne se sert du SSH ‘que’ pour donner la possibilité de cloner des projets sans devoir s’authentifier. Je vais utiliser le port 4443 pour le registry docker.

Pensez à changer letsencrypt['enable'] = true et external_url 'https://gitlabtest.objectif-libre.com' dans le fichier de configuration gitlab /data/gitlab/config/gitlab.rb si vous voulez utiliser Let’s encrypt pour avoir l’HTTPS automatiquement.

Il ne vous reste plus qu’à configurer l’utilisateur root à l’adresse https://gitlabtest.objectif-libre.com et à vous identifier avec pour vérifier que tout fonctionne. Je vous conseille par ailleurs de créer un nouvel utilisateur pour ne pas utiliser le compte root lors de nos tests. J’ai appelé le mien « dev1 ».

Activer le registry docker

Le registry docker intégré à Gitlab n’est pas activé par défaut, il nous faut donc l’activer. Il suffit de paramétrer la variable registry_external_url 'https://gitlabtest.objectif-libre.com:4443' dans le fichier /data/gitlab/config/gitlab.rb. Vous pouvez également utiliser un domaine dédié, mais il est plus simple d’utiliser le même domaine concernant les certificats HTTPS. Si vous choisissez d’utiliser un autre domaine, vous devrez ajouter vos certificats dans le dossier /data/gitlab/config/ssl. Plus d’informations à ce sujet ici.

Redémarez le conteneur gitlab avec docker restart gitlab ou exécutez gitlab-ctl reconfigure depuis le conteneur pour que gitlab active le registry docker. Vous devriez ensuite voir que le registry est activé sur le panel d’administration https://gitlabtest.objectif-libre.com/admin :

Essayez à présent de vous connecter à votre registry gitlab avec docker :

docker login gitlabtest.objectif-libre.com:4443

Utilisation du registry

Le registry docker de Gitlab fonctionne comme n’importe quel autre registre. Vous l’utiliserez probablement de cette façon :

docker build -t myawesomeimage .
docker tag myawesomeimage gitlabtest.objectif-libre.com:4443/dev1/awesomeproject:tag
docker push gitlabtest.objectif-libre.com:4443/dev1/awesomeproject:tag

Vous pouvez également utiliser plusieurs niveaux de profondeur pour vos projets, cela peut être très utile si vous avez besoin de plusieurs images par projet :

gitlabtest.objectif-libre.com:4443/dev1/awesomeproject:tag
gitlabtest.objectif-libre.com:4443/dev1/awesomeproject/microservice1:tag
gitlabtest.objectif-libre.com:4443/dev1/awesomeproject/microservice1/image1:tag

Ajouter un runner Gitlab

Maintenant que nous avons un registry docker, nous allons l’utiliser dans une chaîne d’intégration continue. Le but est d’avoir une image docker automatiquement générée, scannée et poussée sur le registry si et seulement si elle ne contient pas de vulnérabilité importantes. Pour ce faire, nous avons besoin d’un runner Gitlab qui s’occupe d’exécuter la chaîne d’intégration continue. Voilà de quoi en déployer un rapidement :

docker run -d --name gitlab-runner --restart always --privileged \
  -v /path/on/host/runner:/etc/gitlab-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
   gitlab/gitlab-runner:latest

J’ai ici choisi de partager le socket docker de l’hôte dans le conteneur pour éviter d’utiliser docker-in-docker, alias dind. Cet article détaille (en anglais) les raisons de ce choix. Ensuite, nous devons enregistrer le runner pour le lier à notre Gitlab. Vous aurez besoin d’un token que vous trouverez sur la page d’administration des runners https://gitlabtest.objectif-libre.com/admin/runners.

docker exec gitlab-runner gitlab-runner register \
 --non-interactive \
 --url https://gitlabtest.objectif-libre.com \
 --registration-token TOKEN\
 --description "Docker Runner" \
 --tag-list "docker" \
 --executor docker \
 --docker-image "docker:latest" \
 --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
 --docker-privileged

L’option docker-volumes est utilisée pour partager le socket docker au sein des conteneurs qui vont être utilisés pour notre intégration continue. Comme nos conteneurs vont manipuler d’autres conteneurs, le flag de privilège est requis.

Scanner les images avec clair

Clair est un outil fait par CoreOS/RedHat qui est capable de scanner des images docker et de détecter les vulnérabilités connues. Nous allons l’utiliser pour empêcher des images vulnérables d’atteindre un environnement de production.

Créez un projet Gitlab nommé broken. Ce projet va uniquement générer une image docker très vulnérable pour tester si notre intégration continue fonctionne correctement. Voici un Dockerfile type :

FROM ubuntu:14.04

RUN apt-get update

RUN apt-get install -y wget

RUN wget https://launchpad.net/~ubuntu-security/+archive/ubuntu/ppa/+build/7531893/+files/openssl_1.0.1-4ubuntu5.31_amd64.deb && dpkg -i openssl_1.0.1-4ubuntu5.31_amd64.deb

Ensuite, nous allons configurer l’intégration continue avec le fichier .gitlab-ci.yml. Clair a besoin d’une base de données contenant toutes les vulnérabilités connues. Cela prend beaucoup de temps à télécharger, c’est pourquoi nous utilisons une base de données pré-remplie avec toutes les vulnérabilités de la veille. Ce système n’est pas optimal et je vous conseille vivement d’utiliser un serveur clair permanent en production. Voici le fichier gitlab-ci.yml magique :

build_image:
  image: docker:git
  script:
    - docker build --no-cache -t gitlabtest.objectif-libre.com:4443/dev1/broken:testing .

test_image:
  image: docker:git
  script:
    - docker run gitlabtest.objectif-libre.com:4443/dev1/broken:testing bash -c "echo 'This is where you can run your tests'"

scan_image:
  image: docker:git
  before_script:
    - apk update && apk add coreutils
    - docker network create scanning
    - docker run -p 5432:5432 -d --net=scanning --name db arminc/clair-db:$(date -d "yesterday" '+%Y-%m-%d') ; sleep 10
    - docker run -p 6060:6060  --net=scanning --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 ; sleep 10
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN gitlabtest.objectif-libre.com:4443
    - docker run --net=scanning --rm --name=scanner --link=clair:clair -v '/var/run/docker.sock:/var/run/docker.sock' objectiflibre/clair-scanner --clair="http://clair:6060" --ip="scanner" -t Medium gitlabtest.objectif-libre.com:4443/dev1/broken:testing
    - docker tag gitlabtest.objectif-libre.com:4443/dev1/broken:testing gitlabtest.objectif-libre.com:4443/dev1/broken:latest
    - docker push gitlabtest.objectif-libre.com:4443/dev1/broken:latest
  after_script:
    - docker rm -vf db clair
    - docker network rm scanning

Cela peut sembler légèrement trop complexe, mais laissez-moi vous expliquer :

Premièrement, nous lançons la base de données contenant les vulnérabilités de la veille. Encore une fois, utilisez un serveur persistant en production. Nous utilisons une base pré-remplie uniquement pour tester notre intégration continue sans devoir attendre que clair télécharge toutes les vulnérabilités connues. Ensuite, nous créons un réseau docker pour permettre à nos conteneurs de communiquer entre eux. Enfin, nous analysons l’image avec une version conteneurisée de clair-scanner d’arminc et poussons l’image si elle n’est pas vulnérable.

Ceci n’est qu’un exemple, et dans une chaîne d’intégration continue plus réaliste, vous voudrez probablement utiliser les variables d’environnement pour les tags d’images par exemple.

Une fois le fichier .gitlab-ci.yml commit, vous devriez voir dans les logs le résultat du scan:

Conclusion

Mettre en place un scan de vulnérabilité systématique pour chaque projet est certes fastidieux, mais cela permet de prévenir de potentiels vecteurs d’attaques.
Gardez en tête qu’un scan de vulnérabilité est comparable à un antivirus : pas suffisant pour considérer que votre infrastructure est fiable et sécurisée, mais cependant essentiel. Clair ne peut que remonter des vulnérabilités connues. Il vous faut toujours respecter les bonnes pratiques de sécurité.