Skip to content

A la découverte de Docker Scout

Non ce n’est pas des enfants qui font du porte-à-porte pour vous vendre des cookies, promis ! 🍪

Bon, on est tous d’accord pour dire que la sécurité des images et des conteneurs est essentielle pour garantir des déploiements sûrs et efficaces ? Mais comment s’en assurer ? 🤔

Et c’est à ce moment précis que je vous parle de Docker Scout, un outil d’analyse d’images de conteneurs, dans les dernières versions de Docker. 🤩

Qu’est ce que Docker Scout ?

C’est un plugin que l’on peut intégrer directement à la ligne de commande Docker, facilitant le travail de scan. On peut également avoir des détails précis sur la structure et les dépendances de nos images et des informations sur les vulnérabilités potentielles. 

Ce qui m’a attiré avec cet outil, c’est que ce n’est pas un ajout externe comme peut l’être Trivy par exemple. 

Sur le papier je me suis dit “ah ouais, ça peut faire le taf”

Mais je veux en avoir le coeur net ! 👊

Est-ce que Docker Scout va remplacer notre bon vieux Trivy ? C’est ce que l’on va voir dans cet article !

Mise en place de Docker Scout dans une CI

Je ne voyais pas vraiment l’avantage de tester la CLI docker scout, bien qu’elle ait le mérite d’exister. Si ça vous intéresse je vous laisse jeter un oeil à la documentation 🧐

Moi ce qui me chauffe c’est de tester l’outil dans un pipeline GitLabCI. ça tombe bien parce que c’est exactement ce que l’on va faire ! 😅

Pour commencer, j’ai repris les sources du projet démo fourni dans la doc officielle et j’ai initié un repo sur mon GitLab. On s’en servira pour tester Docker Scout via un pipeline.

Chose plutôt cool, on nous fournit déjà le template. Dooooonc, faisons un bon vieux copier/coller dans un fichier .gitlab-ci.yml 🤷‍♂️.

Ce qui nous donne :

docker-build:
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
    
    # Install curl and the Docker Scout CLI
    - |
      apk add --update curl
      curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- 
      apk del curl 
      rm -rf /var/cache/apk/* 
    # Login to Docker Hub required for Docker Scout CLI
    - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin
  script:
    - |
      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
        tag=""
        echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
      else
        tag=":$CI_COMMIT_REF_SLUG"
        echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
      fi
    - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
    
    - |
      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
        # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected
        docker scout cves "$CI_REGISTRY_IMAGE${tag}" --exit-code --only-severity critical,high    
      else
        # Compare image from branch with latest image from the default branch and fail if new critical or high CVEs are detected
        docker scout compare "$CI_REGISTRY_IMAGE${tag}" --to "$CI_REGISTRY_IMAGE:latest" --exit-code --only-severity critical,high --ignore-unchanged
      fi
    
    - docker push "$CI_REGISTRY_IMAGE${tag}"
  rules:
    - if: $CI_COMMIT_BRANCH
      exists:
        - Dockerfile

Ne partez pas en courant, on va regarder ce que ça fait ensemble ! 🏃

Ce fichier permet de construire une image Docker à partir d’un Dockerfile, la pousse vers la registry GitLab et vérifie les vulnérabilités de sécurité avec Docker Scout. Petite subtilité, l’image est build avec le tag latest si l’on est sur la branche principale. Sinon, le tag portera le nom de la branche utilisée lors du lancement de la CI.

Le pipeline est exécuté uniquement si un fichier Dockerfile existe sur la branche sur laquelle le commit a été effectué.

Perso, j’ai apporté quelques ajustements. Notamment sur la condition pour exécuter les commandes docker scout. Je l’ai supprimé. Je souhaite un scan à chaque build, peu importe ma branche.

Le deuxième changement apporté est le nommage des tags. Je ne souhaite pas avoir de tag latest lors des phases de builds sur ma branche principale. Je préfère me baser pour le SHA du commit, c’est plus facile pour trouver le code à partir duquel l’image a été construite. Du coup, la partie script de mon pipeline ressemble désormais à ça :

docker-build:
 image: docker:latest
 stage: build
 services:
   - docker:dind
 before_script:
   - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  
   # Install curl and the Docker Scout CLI
   - |
     apk add --update curl
     curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s --
     apk del curl
     rm -rf /var/cache/apk/*
   # Login to Docker Hub required for Docker Scout CLI
   - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin
 script:
   - |
     tag=":$CI_COMMIT_SHORT_SHA"
     echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
  
   # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected     
   - |
     docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
     docker scout cves "$CI_REGISTRY_IMAGE${tag}" --exit-code --only-severity critical,high   
     docker push "$CI_REGISTRY_IMAGE${tag}"
 rules:
   - if: $CI_COMMIT_BRANCH
     exists:
       - Dockerfile

Maintenant que c’est plus clair, on teste ? 🧪

Mmmmh, deux minutes papillon, il faut que l’on définisse quelques variables pour que l’on puisse se connecter sur le Docker Hub. Ajoutons nos deux variables $DOCKER_HUB_PAT et $DOCKER_HUB_USER (il vous faut un compte pour obtenir ces infos)

Ensuite, il suffit d’aller sur notre repo GitLab, dans Settings > CI/CD > Variables est de les définir 

Maintenant oui, on peut tester ! 😀

Vu que j’ai bien un Dockerfile sur ma branche, j’ai juste à push mon fichier .gitlab-ci.yml pour lancer le pipeline. 👍

Analysons le résultat de scan

Notre pipeline s’est bien exécuté. 🥳
Si vous êtes curieux le résultat est ici https://gitlab.com/Gribhb/demo-docker-scout/-/jobs/6508931536

Comme on peut le voir, c’est quand même assez verbeux. Il y a beaucoup d’infos. Et on a trié uniquement sur les vulns HIGH et CRITICAL 😰.

Bon, on a tout de même un récap à la fin, qui permet de voir rapidement où l’on en est :

 LOW       0  
 MEDIUM    0  
 HIGH      18 
 CRITICAL  2

Petit détail, qui m’a agréablement surpris c’est ce message, tout à la fin :

What's Next?
 View base image update recommendations → docker scout recommendations registry.gitlab.com/gribhb/demo-docker-scout:306m8f0f

On ne nous laisse pas en plan en mode, “tiens, voilà tes failles, bon courage 👋”. 

Non, là, docker scout nous indique les remédiations pour chaque recommandation. On a également des informations sur la réduction des vulnérabilités ou la diminution de la taille de l’image. Je trouve ça vraiment cool ! 🤩
De plus, pour chaque CVE, docker scout nous affiche les versions affectées et celles qui permettent de fixer la vulnérabilité.

   ✗ HIGH CVE-2022-24999 [Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')]
     https://scout.docker.com/v/CVE-2022-24999?s=github&n=qs&t=npm&vr=%3E%3D6.7.0%2C%3C6.7.3
     Affected range : >=6.7.0                                      
                    : <6.7.3                                       
     Fixed version  : 6.7.3

Bon bon bon, pour un premier test c’est assez cool, mais j’ai bien envie d’aller plus loin. De rendre ça plus flexible, pas vous ? 😏

On va être honnête, je ne suis pas fan d’avoir des commandes curl pour installer l’outil dans ma CI de build, ça n’a rien à faire là. Moi, ce que je veux c’est builder mon image et faire un scan de vulnérabilité, tout en ayant un pipeline visible et clair. 😅

Et si on faisait un template GitLabCI ?

Du coup, ce que je vous propose, c’est de faire un template que l’on pourra utiliser dans nos CI de build. On aura juste à paramétrer quelques variables (déso, c’est pas magique non plus 😜) notamment pour se connecter au Docker Hub, choisir le niveau de sévérité des vulnérabilités ou simplement quelle commande docker scout l’on souhaite exécuter.

Allez, on retrousse nos manches et au boulot !! 🛠️

docker-build:
 image: docker:latest
 stage: build
 services:
   - docker:dind
 before_script:
   - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  
   # Install curl and the Docker Scout CLI
   - |
     apk add --update curl
     curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s --
     apk del curl
     rm -rf /var/cache/apk/*
   # Login to Docker Hub required for Docker Scout CLI
   - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin
 script:
   - |
     tag=":$CI_COMMIT_SHORT_SHA"
     echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
        
   - |
     docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
     echo "$COMMAND" | tr ',' '\n' | while read -r cmd; do case "$cmd" in "quickview") docker scout quickview "$CI_REGISTRY_IMAGE${tag}";; "cves") docker scout cves "$CI_REGISTRY_IMAGE${tag}" ${EXIT_CODE:+--exit-code} --only-severity $ONLY_SEVERITY;;"recommendations") docker scout recommendations "$CI_REGISTRY_IMAGE${tag}";; *) echo "Unknown Command: $cmd". Available commands : quickview,cves,recommendations && exit 1;; esac; done &&  \
     docker push "$CI_REGISTRY_IMAGE${tag}"
rules:
  - if: $CI_COMMIT_BRANCH
    changes:
      - README.md
    when: never
  - if: $CI_COMMIT_BRANCH
    exists:
      - Dockerfile

Je suis parti de notre pipeline précédent, en y apportant quelques modifications, notamment sur cette partie :

   - |
     docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
     echo "$COMMAND" | tr ',' '\n' | while read -r cmd; do case "$cmd" in "quickview") docker scout quickview "$CI_REGISTRY_IMAGE${tag}";; "cves") docker scout cves "$CI_REGISTRY_IMAGE${tag}" ${EXIT_CODE:+--exit-code} --only-severity $ONLY_SEVERITY;;"recommendations") docker scout recommendations "$CI_REGISTRY_IMAGE${tag}";; *) echo "Unknown Command: $cmd". Available commands : quickview,cves,recommendations && exit 1;; esac; done &&  \
     docker push "$CI_REGISTRY_IMAGE${tag}"
rules:
  - if: $CI_COMMIT_BRANCH
    changes:
      - README.md
    when: never
  - if: $CI_COMMIT_BRANCH
    exists:
      - Dockerfile

Ok, au premier abord ça peut faire peur j’avoue… Maiiiiis, on va éclaircir tout ça. Ce n’est pas si compliqué vous allez voir. 😂

echo "$COMMAND" | tr ',' '\n' | while read -r cmd; do ... done : Cette partie prend la valeur de la variable COMMAND, la divise en utilisant la virgule comme séparateur (tr ',' '\n') et les lit une par une (while read -r cmd). Ensuite, pour chaque commande, elle exécute une action spécifique.

case "$cmd" in ... esac : On vérifie la valeur de la variable $cmd pour voir si elle correspond à l’une des valeurs suivantes : “quickview”, “cves” ou “recommendations” qui sont les commandes Docker Scout.

Par exemple, Si la valeur de $cmd correspond à “quickview” la commande exécutée sera : docker scout quickview "$CI_REGISTRY_IMAGE${tag}

Si la valeur de $cmd ne correspond à aucune des valeurs attendues, elle affiche un message d’erreur et le pipeline s’arrête.

Une fois toutes les commandes exécutées, on pousse l’image Docker vers le registre spécifié par la variable $CI_REGISTRY_IMAGE.

Je me permets de faire un petit aparté sur le bout de commande :

docker scout cves "$CI_REGISTRY_IMAGE${tag}" ${EXIT_CODE:+--exit-code} --only-severity $ONLY_SEVERITY

ça veut tout simplement dire que l’on exécute  docker scout avec l’option “cves”. J’inclus l’argument --exit-code uniquement si la variable EXIT_CODE est définie.

Et comment utilise-t-on ce beau template ?

Tout simplement comment ça :

include: "/template_dockerscout_ci/dockerscout.gitlab-ci.yml"

variables:
  ONLY_SEVERITY: "high,critical"
  EXIT_CODE: "true"
  COMMAND: "quickview, cves"

Voici le contenu de notre fichier .gitlab-ci.yml . C’est quand même plus léger que ce que l’on avait au départ non ? 😏

Pour ceux qui seraient moins familiers avec les templates GitLab, j’inclue mon fichier de template (là où il y a toute notre magie, nos commandes docker scout etc…). Ensuite, j’ai juste à définir les variables. Et le tour est joué ! 👌

Vous pouvez retrouver toutes les sources sur ce repo GitLab : https://gitlab.com/Gribhb/demo-docker-scout

Conclusion

Bien évidemment il y a pleiiiiiin d’autres outils de scan d’image Docker, on peut citer Trivy qui est sûrement l’un des plus connus.

En tout cas, je ne suis pas mécontent de l’outil. Je trouve qu’il apporte vraiment un aspect sécu dans nos pipelines. En revanche, je ne suis pas fan de rendre obligatoire la connexion au Docker Hub… Mais bon, c’est le jeu on va dire.

Je trouve que l’intégration à GitLab mais pas folle. Avec GitHub, on peut avoir des rapports très stylés bien mis en page automatiquement en artefact à la fin du pipeline. chose que l’on n’a pas sur GitLab 🙁

Tout ça pour dire que je vais continuer d’utiliser Trivy dans mes pipelines, du moins pour l’instant. Bien que  l’outil docker scout reste intéressant avec une symbiose parfaite entre nos commandes docker build et docker scout ce qui nous permet d’inclure le scan de vulnérabilité directement avec le build et non dans deux jobs séparés. 

L’avantage que je note également c’est que l’on n’est pas obligé de push l’image dans une registry pour effectuer le scan car le build/scan se fait au sein du même job. Chose (corrigez-moi si je me trompe), qu’il n’est pas faisable avec Trivy. À moins d’avoir un job contenant docker ET Trivy dans la même image… 

Voilà, je pense avoir fait les présentations avec Docker Scout. J’espère que vous avez apprécié cet article ! 

Et n’oubliez pas, voir les vulnérabilités n’est pas suffisant hein! Il faut prendre le temps de les fixer 😛

Published inNon classé