Méthodes et exemples de mise en œuvre des utilitaires de contrôle de sécurité Docker



Bonjour, Habr!



Dans la réalité moderne, en raison du rôle croissant de la conteneurisation dans les processus de développement, la question d'assurer la sécurité des différentes étapes et entités associées aux conteneurs n'est pas en dernier lieu. Effectuer des vérifications en mode manuel prend du temps, il serait donc bien de prendre au moins les étapes initiales pour automatiser ce processus.



Dans cet article, je partagerai des scripts prêts à l'emploi pour la mise en œuvre de plusieurs utilitaires de sécurité Docker et des instructions sur la façon de déployer un petit support de démonstration pour tester ce processus. Vous pouvez utiliser les ressources pour expérimenter comment organiser le processus de test de sécurité pour les images et les instructions Dockerfile. Il est clair que l'infrastructure de développement et de mise en œuvre est différente pour tout le monde, donc ci-dessous je vais donner plusieurs options possibles.



Utilitaires de contrôle de sécurité



Il existe de nombreuses applications d'assistance et scripts différents qui testent divers aspects de l'infrastructure Docker. Certains d'entre eux ont déjà été décrits dans l'article précédent ( https://habr.com/ru/company/swordfish_security/blog/518758/#docker-security ), et dans ce document, je voudrais me concentrer sur trois d'entre eux qui couvrent les principaux fait partie des exigences de sécurité pour les images Docker qui sont créées pendant le développement. En outre, je montrerai également un exemple de la façon dont ces trois utilitaires peuvent être connectés en un seul pipeline pour effectuer des contrôles de sécurité.



Hadolint

https://github.com/hadolint/hadolint



Un utilitaire de console assez simple qui permet, en première approximation, d'évaluer l'exactitude et la sécurité des instructions Dockerfile (par exemple, en utilisant uniquement des registres d'images autorisés ou en utilisant sudo).



Sortie de l'utilitaire Hadolint



Dockle

https://github.com/goodwithtech/dockle



Utilitaire de console qui fonctionne avec une image (ou avec une archive tar enregistrée d'une image) qui vérifie l'exactitude et la sécurité d'une image spécifique en tant que telle, en analysant ses couches et sa configuration - quels utilisateurs sont créés, quelles instructions utilisé quels volumes sont montés, la présence d'un mot de passe vide, etc. Alors que le nombre de contrôles n'est pas très grand et est basé sur plusieurs de ses propres contrôles et recommandations du CIS (Center for Internet Security) Benchmark for Docker.





Trivy

https://github.com/aquasecurity/trivy



Cet utilitaire vise à trouver deux types de vulnérabilités - les problèmes de construction du système d'exploitation (pris en charge par Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) et les problèmes de dépendance (Gemfile.lock, Pipfile). lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy peut analyser à la fois l'image dans le référentiel et l'image locale, ainsi que l'analyse basée sur le fichier .tar transféré avec l'image Docker.







Options de mise en œuvre pour les utilitaires



Afin d'essayer les applications décrites dans des conditions isolées, je fournirai des instructions pour installer tous les utilitaires dans un processus simplifié.



L'idée principale est de montrer comment vous pouvez implémenter la validation automatique du contenu des images Dockerfile et Docker créées pendant le développement.



Le contrôle lui-même comprend les étapes suivantes:

  1. Vérification de l'exactitude et de la sécurité des instructions Dockerfile - Utilisation du linter Hadolint
  2. Vérification de l'exactitude et de la sécurité des images cibles et intermédiaires - à l'aide de l'utilitaire Dockle
  3. Recherche de vulnérabilités bien connues (CVE) dans l'image de base et un certain nombre de dépendances - à l'aide de l'utilitaire Trivy


Plus loin dans l'article, je vais donner trois options pour implémenter ces étapes:

Premièrement, en configurant le pipeline CI / CD à l'aide de l'exemple de GitLab (avec une description du processus de génération d'une instance de test).

Le second utilise un script shell.

Le troisième consiste à créer une image Docker pour la numérisation des images Docker.

Vous pouvez choisir l'option qui vous convient le mieux, la transférer dans votre infrastructure et l'adapter à vos besoins.



Tous les fichiers nécessaires et les instructions supplémentaires se trouvent également dans le référentiel: https://github.com/Swordfish-Security/docker_cicd



Intégration dans GitLab CI / CD



Dans la première option, nous verrons comment vous pouvez implémenter des contrôles de sécurité en utilisant le système de référentiel GitLab comme exemple. Ici, nous allons parcourir les étapes et analyser comment configurer un environnement de test avec GitLab à partir de zéro, créer un processus d'analyse et exécuter des utilitaires pour vérifier un fichier Dockerfile de test et une image aléatoire - l'application JuiceShop.



Installation de GitLab

1. Installez Docker:

sudo apt-get update && sudo apt-get install docker.io


2. Ajoutez l'utilisateur actuel au groupe docker afin de pouvoir travailler avec docker et non via sudo:

sudo addgroup <username> docker


3. Trouvez votre adresse IP:

ip addr


4. Installez et exécutez GitLab dans un conteneur, en remplaçant l'adresse IP dans hostname par la vôtre:

docker run --detach \
--hostname 192.168.1.112 \
--publish 443:443 --publish 80:80 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest


Nous attendons que GitLab termine toutes les procédures d'installation nécessaires (vous pouvez surveiller le processus via la sortie du fichier journal: docker logs -f gitlab).



5. Ouvrez votre adresse IP locale dans le navigateur et affichez une page avec une proposition de modification du mot de passe de l'utilisateur root:



définissez un nouveau mot de passe et accédez à GitLab.



6. Créez un nouveau projet, par exemple cicd-test et initialisez-le avec le fichier de démarrage README.md :



7. Nous devons maintenant installer GitLab Runner: un agent qui lancera toutes les opérations nécessaires sur demande.

Téléchargez la dernière version (dans ce cas, pour Linux 64 bits):

sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64


8. Rendez-le exécutable:

sudo chmod +x /usr/local/bin/gitlab-runner


9. Ajoutez l'utilisateur du système d'exploitation pour le Runner et démarrez le service:

sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start


Ça devrait ressembler a quelque chose comme ca:



local@osboxes:~$ sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
Runtime platform arch=amd64 os=linux pid=8438 revision=0e5417a3 version=12.0.1
local@osboxes:~$ sudo gitlab-runner start
Runtime platform arch=amd64 os=linux pid=8518 revision=0e5417a3 version=12.0.1


10. Maintenant, nous enregistrons le Runner afin qu'il puisse interagir avec notre instance GitLab.

Pour ce faire, ouvrez la page Settings-CI / CD (http: // OUR_ IP_ADDRESS / root / cicd-test / - / settings / ci_cd) et dans l'onglet Runners, nous trouvons l'URL et le jeton d'enregistrement:



11. Enregistrez le Runner en remplaçant l'URL et le jeton d'enregistrement:

sudo gitlab-runner register \
--non-interactive \
--url "http://<URL>/" \
--registration-token "<Registration Token>" \
--executor "docker" \
--docker-privileged \
--docker-image alpine:latest \
--description "docker-runner" \
--tag-list "docker,privileged" \
--run-untagged="true" \
--locked="false" \
--access-level="not_protected"


En conséquence, nous obtenons un GitLab prêt à l'emploi, dans lequel nous devons ajouter des instructions pour démarrer nos utilitaires. Dans ce cas de démonstration, nous n'avons pas d'étapes pour construire l'application et sa conteneurisation, mais dans un environnement réel, elles précéderont les étapes de numérisation et généreront des images et Dockerfile pour l'analyse.



Configuration du pipeline



1. Ajoutez au référentiel les fichiers mydockerfile.df (il s'agit d'un Dockerfile de test que nous vérifierons) et le fichier de configuration du processus GitLab CI / CD .gitlab-cicd.yml , qui répertorie les instructions pour les scanners (notez le point dans le nom du fichier ).



Le fichier de configuration YAML contient des instructions pour exécuter trois utilitaires (Hadolint, Dockle et Trivy) qui analyseront le Dockerfile sélectionné et l'image spécifiée dans la variable DOCKERFILE. Tous les fichiers nécessaires peuvent être extraits du référentiel: https://github.com/Swordfish-Security/docker_cicd/



Extrait de mydockerfile.df (il s'agit d'un fichier abstrait avec un ensemble d'instructions arbitraires juste pour montrer comment fonctionne l'utilitaire). Lien direct vers le fichier: mydockerfile.df



Contenu de mydockerfile.df
FROM amd64/node:10.16.0-alpine@sha256:f59303fb3248e5d992586c76cc83e1d3700f641cbcd7c0067bc7ad5bb2e5b489 AS tsbuild
COPY package.json .
COPY yarn.lock .
RUN yarn install
COPY lib lib
COPY tsconfig.json tsconfig.json
COPY tsconfig.app.json tsconfig.app.json
RUN yarn build
FROM amd64/ubuntu:18.04@sha256:eb70667a801686f914408558660da753cde27192cd036148e58258819b927395
LABEL maintainer="Rhys Arkins <rhys@arkins.net>"
LABEL name="renovate"
...
COPY php.ini /usr/local/etc/php/php.ini
RUN cp -a /tmp/piik/* /var/www/html/
RUN rm -rf /tmp/piwik
RUN chown -R www-data /var/www/html
ADD piwik-cli-setup /piwik-cli-setup
ADD reset.php /var/www/html/
## ENTRYPOINT ##
ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
USER root


La configuration YAML ressemble à ceci (le fichier lui-même peut être extrait du lien direct ici: .gitlab-ci.yml ):



Contenu du .Gitlab-ci.yml
variables:
    DOCKER_HOST: "tcp://docker:2375/"
    DOCKERFILE: "mydockerfile.df" # name of the Dockerfile to analyse   
    DOCKERIMAGE: "bkimminich/juice-shop" # name of the Docker image to analyse
    # DOCKERIMAGE: "knqyf263/cve-2018-11235" # test Docker image with several CRITICAL CVE
    SHOWSTOPPER_PRIORITY: "CRITICAL" # what level of criticality will fail Trivy job
    TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # where to cache Trivy database of vulnerabilities for faster reuse
    ARTIFACT_FOLDER: "$CI_PROJECT_DIR"
 
services:
    - docker:dind # to be able to build docker images inside the Runner
 
stages:
    - scan
    - report
    - publish
 
HadoLint:
    # Basic lint analysis of Dockerfile instructions
    stage: scan
    image: docker:git
 
    after_script:
    - cat $ARTIFACT_FOLDER/hadolint_results.json
 
    script:
    - export VERSION=$(wget -q -O - https://api.github.com/repos/hadolint/hadolint/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
    - wget https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64 && chmod +x hadolint-Linux-x86_64
     
    # NB: hadolint will always exit with 0 exit code
    - ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0
 
    artifacts:
        when: always # return artifacts even after job failure       
        paths:
        - $ARTIFACT_FOLDER/hadolint_results.json
 
Dockle:
    # Analysing best practices about docker image (users permissions, instructions followed when image was built, etc.)
    stage: scan   
    image: docker:git
 
    after_script:
    - cat $ARTIFACT_FOLDER/dockle_results.json
 
    script:
    - export VERSION=$(wget -q -O - https://api.github.com/repos/goodwithtech/dockle/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
    - wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz && tar zxf dockle_${VERSION}_Linux-64bit.tar.gz
    - ./dockle --exit-code 1 -f json --output $ARTIFACT_FOLDER/dockle_results.json $DOCKERIMAGE   
     
    artifacts:
        when: always # return artifacts even after job failure       
        paths:
        - $ARTIFACT_FOLDER/dockle_results.json
 
Trivy:
    # Analysing docker image and package dependencies against several CVE bases
    stage: scan   
    image: docker:git
 
    script:
    # getting the latest Trivy
    - apk add rpm
    - export VERSION=$(wget -q -O - https://api.github.com/repos/knqyf263/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
    - wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz && tar zxf trivy_${VERSION}_Linux-64bit.tar.gz
     
    # displaying all vulnerabilities w/o failing the build
    - ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE    
    
    # write vulnerabilities info to stdout in human readable format (reading pure json is not fun, eh?). You can remove this if you don't need this.
    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE    
 
    # failing the build if the SHOWSTOPPER priority is found
    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE
         
    artifacts:
        when: always # return artifacts even after job failure
        paths:
        - $ARTIFACT_FOLDER/trivy_results.json
 
    cache:
        paths:
        - .cache
 
Report:
    # combining tools outputs into one HTML
    stage: report
    when: always
    image: python:3.5
     
    script:
    - mkdir json
    - cp $ARTIFACT_FOLDER/*.json ./json/
    - pip install json2html
    - wget https://raw.githubusercontent.com/shad0wrunner/docker_cicd/master/convert_json_results.py
    - python ./convert_json_results.py
     
    artifacts:
        paths:
        - results.html


Si nécessaire, vous pouvez également numériser les images enregistrées en tant qu'archive .tar (cependant, vous devrez modifier les paramètres d'entrée des utilitaires dans le fichier YAML)



NB: Trivy rpm git. RedHat-based .


2. Après avoir ajouté les fichiers au référentiel, conformément aux instructions de notre fichier de configuration, GitLab démarrera automatiquement le processus de construction et d'analyse. Sur l'onglet CI / CD → Pipelines, vous pouvez voir la progression des instructions.



En conséquence, nous avons quatre tâches. Trois d'entre eux traitent directement de l'analyse et le dernier (Rapport) recueille un rapport simple à partir de fichiers dispersés avec les résultats de l'analyse.



Par défaut, Trivy arrête de s'exécuter si des vulnérabilités CRITIQUES sont trouvées dans l'image ou les dépendances. Dans le même temps, Hadolint renvoie toujours Success le code d'exécution, car à la suite de son exécution, il y a toujours des commentaires, ce qui conduit à l'arrêt de la construction.



En fonction de vos besoins spécifiques, vous pouvez configurer le code de sortie de sorte que ces utilitaires arrêtent également le processus de génération lorsqu'ils détectent des problèmes d'une certaine gravité. Dans notre cas, la compilation ne s'arrêtera que si Trivy détecte une vulnérabilité critique que nous avons spécifiée dans la variable SHOWSTOPPER dans .gitlab-ci.yml .





Le résultat de l'opération de chaque utilitaire peut être visualisé dans le journal de chaque tâche d'analyse, directement dans les fichiers json de la section artefacts, ou dans un simple rapport HTML (plus à ce sujet ci-dessous):





3. Pour présenter les rapports de l'utilitaire sous une forme légèrement plus lisible par l'homme, un petit script Python est utilisé pour conversion de trois fichiers json en un seul fichier HTML avec un tableau des défauts.

Ce script est lancé par une tâche de rapport distincte et son artefact final est un fichier HTML avec un rapport. La source du script se trouve également dans le référentiel et vous pouvez l'adapter à vos besoins, couleurs, etc.





Script shell



La deuxième option convient aux cas où vous devez vérifier les images Docker en dehors du système CI / CD, ou vous devez avoir toutes les instructions sous une forme qui peut être exécutée directement sur l'hôte. Cette option est couverte par un script shell prêt à l'emploi qui peut être exécuté sur une machine virtuelle propre (ou même réelle). Le script suit les mêmes instructions que le gitlab-runner ci-dessus.



Pour que le script fonctionne correctement, Docker doit être installé sur le système et l'utilisateur actuel doit être dans le groupe Docker.



Le script lui-même peut être pris ici: docker_sec_check.sh



Au début du fichier, des variables sont utilisées pour définir quelle image doit être analysée et quels défauts critiques entraîneront la sortie de l'utilitaire Trivy avec le code d'erreur spécifié.



Pendant l'exécution du script, tous les utilitaires seront téléchargés dans le répertoire docker_tools , les résultats de leur travail - dans le répertoire docker_tools / json , et le code HTML avec le rapport sera dans le fichier results.html .



Exemple de sortie de script
~/docker_cicd$ ./docker_sec_check.sh

[+] Setting environment variables
[+] Installing required packages
[+] Preparing necessary directories
[+] Fetching sample Dockerfile
2020-10-20 10:40:00 (45.3 MB/s) - ‘Dockerfile’ saved [8071/8071]
[+] Pulling image to scan
latest: Pulling from bkimminich/juice-shop
[+] Running Hadolint
...
Dockerfile:205 DL3015 Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:248 DL3002 Last USER should not be root
...
[+] Running Dockle
...
WARN    - DKL-DI-0006: Avoid latest tag
        * Avoid 'latest' tag
INFO    - CIS-DI-0005: Enable Content trust for Docker
        * export DOCKER_CONTENT_TRUST=1 before docker pull/build
...
[+] Running Trivy
juice-shop/frontend/package-lock.json
=====================================
Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)

+---------------------+------------------+----------+---------+-------------------------+
|       LIBRARY       | VULNERABILITY ID | SEVERITY | VERSION |             TITLE       |
+---------------------+------------------+----------+---------+-------------------------+
| object-path         | CVE-2020-15256   | HIGH     | 0.11.4  | Prototype pollution in  |
|                     |                  |          |         | object-path             |
+---------------------+------------------+          +---------+-------------------------+
| tree-kill           | CVE-2019-15599   |          | 1.2.2   | Code Injection          |
+---------------------+------------------+----------+---------+-------------------------+
| webpack-subresource | CVE-2020-15262   | LOW      | 1.4.1   | Unprotected dynamically |
|                     |                  |          |         | loaded chunks           |
+---------------------+------------------+----------+---------+-------------------------+

juice-shop/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)

...

juice-shop/package-lock.json
============================
Total: 5 (CRITICAL: 5)

...
[+] Removing left-overs
[+] Making the output look pretty
[+] Converting JSON results
[+] Writing results HTML
[+] Clean exit ============================================================
[+] Everything is done. Find the resulting HTML report in results.html




Image Docker avec tous les utilitaires



Comme troisième alternative, j'ai compilé deux fichiers Docker simples pour créer une image avec des utilitaires de sécurité. Un Dockerfile aidera à créer un ensemble pour scanner une image à partir du référentiel, le second (Dockerfile_tar) - construire un ensemble pour scanner un fichier tar avec une image.



1. Nous prenons le fichier Docker correspondant et les scripts du référentiel https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile .

2. Exécutez-le pour l'assemblage:

docker build -t dscan:image -f docker_security.df .


3. Après la fin de l'assemblage, créez un conteneur à partir de l'image. En même temps, nous passons la variable d'environnement DOCKERIMAGE avec le nom de l'image qui nous intéresse et montons le Dockerfile que nous voulons analyser depuis notre machine vers le fichier / Dockerfile (notez qu'un chemin absolu vers ce fichier est requis):

docker run --rm -v $(pwd)/results:/results -v $(pwd)/docker_security.df:/Dockerfile -e DOCKERIMAGE="bkimminich/juice-shop" dscan:image



[+] Setting environment variables
[+] Running Hadolint
/Dockerfile:3 DL3006 Always tag the version of an image explicitly
[+] Running Dockle
WARN    - DKL-DI-0006: Avoid latest tag
        * Avoid 'latest' tag
INFO    - CIS-DI-0005: Enable Content trust for Docker
        * export DOCKER_CONTENT_TRUST=1 before docker pull/build
INFO    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image
        * not found HEALTHCHECK statement
INFO    - DKL-LI-0003: Only put necessary files
        * unnecessary file : juice-shop/node_modules/sqlite3/Dockerfile
        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm64/Dockerfile
        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm/Dockerfile
[+] Running Trivy
...
juice-shop/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
[+] Making the output look pretty
[+] Starting the main module ============================================================
[+] Converting JSON results
[+] Writing results HTML
[+] Clean exit ============================================================
[+] Everything is done. Find the resulting HTML report in results.html


résultats



Nous n'avons couvert qu'un seul ensemble d'outils de base pour la numérisation des artefacts Docker, qui, à mon avis, couvre assez efficacement une partie décente des exigences de sécurité d'image. Il existe de nombreux autres outils gratuits et payants qui peuvent effectuer les mêmes vérifications, rédiger de beaux rapports ou travailler uniquement en mode console, couvrir les systèmes de gestion de conteneurs, etc. Un aperçu de ces outils et comment les intégrer peut apparaître un peu plus tard.



Le côté positif de l'ensemble d'outils, qui est décrit dans l'article, est qu'ils sont tous construits sur du code open source et que vous pouvez les expérimenter ainsi que d'autres outils similaires pour trouver ce qui convient exactement à vos besoins et à vos fonctionnalités d'infrastructure. Bien sûr, toutes les vulnérabilités qui seront trouvées doivent être étudiées pour l'applicabilité dans des conditions spécifiques, mais c'est un sujet pour un futur grand article.



J'espère que ce tutoriel, ces scripts et utilitaires vous aideront et deviendront un point de départ pour créer une infrastructure plus sécurisée dans le domaine de la conteneurisation.



All Articles