Métodos y ejemplos de implementación de utilidades de verificación de seguridad de Docker



¡Hola, Habr!



En la realidad moderna, debido al papel cada vez más importante de la contenerización en los procesos de desarrollo, la cuestión de garantizar la seguridad de las diversas etapas y entidades asociadas a los contenedores no está en el último lugar. Realizar comprobaciones en modo manual lleva mucho tiempo, por lo que sería bueno seguir al menos los pasos iniciales para automatizar este proceso.



En este artículo, compartiré scripts listos para implementar para implementar varias utilidades de seguridad de Docker e instrucciones sobre cómo implementar un pequeño soporte de demostración para probar este proceso. Puede usar los recursos para experimentar cómo organizar el proceso de prueba de seguridad para imágenes e instrucciones de Dockerfile. Está claro que la infraestructura de desarrollo e implementación es diferente para todos, por lo que a continuación daré varias opciones posibles.



Utilidades de control de seguridad



Hay muchas aplicaciones auxiliares y scripts diferentes que prueban varios aspectos de la infraestructura de Docker. Algunos de ellos ya se han descrito en el artículo anterior ( https://habr.com/ru/company/swordfish_security/blog/518758/#docker-security ), y en este material me gustaría centrarme en tres de ellos que cubren los principales parte de los requisitos de seguridad para las imágenes de Docker que se crean durante el desarrollo. Además, también mostraré un ejemplo de cómo estas tres utilidades se pueden conectar en una tubería para realizar controles de seguridad.



Hadolint

https://github.com/hadolint/hadolint



Una utilidad de consola bastante simple que ayuda, como primera aproximación, a evaluar la exactitud y seguridad de las instrucciones de Dockerfile (por ejemplo, usando solo registros de imágenes autorizados o usando sudo).



Salida de la utilidad Hadolint



Dockle

https://github.com/goodwithtech/dockle



Utilidad de consola que trabaja con una imagen (o con un archivo tar guardado de una imagen) que verifica la exactitud y seguridad de una imagen específica como tal, analizando sus capas y configuración - qué usuarios son creados, qué instrucciones utiliza qué volúmenes se montan, la presencia de una contraseña vacía, etc. Si bien el número de comprobaciones no es muy grande y se basa en varias de sus propias comprobaciones y recomendaciones del CIS (Center for Internet Security) Benchmark for Docker.





Trivy

https://github.com/aquasecurity/trivy



Esta utilidad tiene como objetivo encontrar dos tipos de vulnerabilidades: problemas de compilación del sistema operativo (compatible con Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) y problemas de dependencia (Gemfile.lock, Pipfile. lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy puede escanear tanto la imagen en el repositorio como la imagen local, así como escanear basándose en el archivo .tar transferido con la imagen de Docker.







Opciones de implementación para servicios públicos



Para probar las aplicaciones descritas en condiciones aisladas, proporcionaré instrucciones para instalar todas las utilidades en un proceso simplificado.



La idea principal es demostrar cómo se puede implementar la validación automática de contenido de las imágenes de Dockerfile y Docker que se crean durante el desarrollo.



La verificación en sí consta de los siguientes pasos:

  1. Comprobación de la exactitud y seguridad de las instrucciones de Dockerfile - utilizando el linter Hadolint
  2. Comprobación de la exactitud y seguridad de las imágenes intermedias y de destino mediante la utilidad Dockle
  3. Comprobación de vulnerabilidades conocidas (CVE) en la imagen base y una serie de dependencias con la utilidad Trivy


Más adelante en el artículo, daré tres opciones para implementar estos pasos:

Primero, configurando el canal de CI / CD usando el ejemplo de GitLab (con una descripción del proceso de generar una instancia de prueba).

El segundo es utilizar un script de shell.

El tercero es construir una imagen de Docker para escanear imágenes de Docker.

Puedes elegir la opción que más te convenga, trasladarla a tu infraestructura y adaptarla a tus necesidades.



Todos los archivos necesarios e instrucciones adicionales también se encuentran en el repositorio: https://github.com/Swordfish-Security/docker_cicd



Integración en GitLab CI / CD



En la primera opción, veremos cómo puede implementar controles de seguridad usando el ejemplo del sistema de repositorio GitLab. Aquí repasaremos los pasos y analizaremos cómo configurar un entorno de prueba con GitLab desde cero, crear un proceso de escaneo y ejecutar utilidades para verificar un Dockerfile de prueba y una imagen aleatoria: la aplicación JuiceShop.



Instalación de GitLab

1. Instale Docker:

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


2. Agregue el usuario actual al grupo de la ventana acoplable para que pueda trabajar con la ventana acoplable, no a través de sudo:

sudo addgroup <username> docker


3. Encuentra tu IP:

ip addr


4. Instale y ejecute GitLab en un contenedor, reemplazando la dirección IP en el nombre de host con la suya propia:

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


Estamos esperando que GitLab complete todos los procedimientos de instalación necesarios (puede seguir el proceso a través de la salida del archivo de registro: docker logs -f gitlab).



5. Abra su IP local en el navegador y vea una página con una propuesta para cambiar la contraseña para el usuario root:



Establezca una nueva contraseña y vaya a GitLab.



6. Cree un nuevo proyecto, por ejemplo cicd-test e inicialícelo con el archivo de inicio README.md :



7. Ahora necesitamos instalar GitLab Runner: un agente que lanzará todas las operaciones necesarias a pedido.

Descargue la última versión (en este caso, para Linux de 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.Hazlo ejecutable:

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


9. Agregue el usuario del sistema operativo para Runner e inicie el servicio:

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


Debería verse algo como esto:



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. Ahora registramos el Runner para que pueda interactuar con nuestra instancia de GitLab.

Para hacer esto, abra la página Settings-CI / CD (http: // OUR_ IP_ADDRESS / root / cicd-test / - / settings / ci_cd) y en la pestaña Corredores busque la URL y el token de registro:



11. Registre el corredor sustituyendo el URL y el token de registro:

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"


Como resultado, obtenemos un GitLab listo para usar, en el que necesitamos agregar instrucciones para iniciar nuestras utilidades. En este caso de demostración, no tenemos pasos para construir la aplicación y su contenedorización, pero en un entorno real precederán los pasos de escaneo y generarán imágenes y Dockerfile para su análisis.



Configuración de la canalización



1. Agregue al repositorio los archivos mydockerfile.df (este es un Dockerfile de prueba que verificaremos) y el archivo de configuración del proceso GitLab CI / CD .gitlab-cicd.yml , que enumera las instrucciones para los escáneres (observe el punto en el nombre del archivo ).



El archivo de configuración YAML contiene instrucciones para ejecutar tres utilidades (Hadolint, Dockle y Trivy) que analizarán el Dockerfile seleccionado y la imagen especificada en la variable DOCKERFILE. Todos los archivos necesarios se pueden tomar del repositorio: https://github.com/Swordfish-Security/docker_cicd/



Extracto de mydockerfile.df (este es un archivo abstracto con un conjunto de instrucciones arbitrarias solo para demostrar cómo funciona la utilidad). Enlace directo al archivo: mydockerfile.df



Contenido 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 configuración YAML se ve así (el archivo en sí se puede tomar desde el enlace directo aquí: .gitlab-ci.yml ):



.Gitlab-ci.yml contenido
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 es necesario, también puede escanear las imágenes guardadas como un archivo .tar (sin embargo, deberá cambiar los parámetros de entrada para las utilidades en el archivo YAML)



NB: Trivy rpm git. RedHat-based .


2. Después de agregar los archivos al repositorio, de acuerdo con las instrucciones en nuestro archivo de configuración, GitLab iniciará automáticamente el proceso de construcción y escaneo. En la pestaña CI / CD → Pipelines, puede ver el progreso de las instrucciones.



Como resultado, tenemos cuatro tareas. Tres de ellos se ocupan directamente del escaneo y el último (Informe) recopila un informe simple de archivos dispersos con los resultados del escaneo.



De forma predeterminada, Trivy deja de ejecutarse si se encuentran vulnerabilidades CRÍTICAS en la imagen o dependencias. Al mismo tiempo, Hadolint siempre devuelve Success el código de ejecución, ya que como resultado de su ejecución, siempre hay comentarios, lo que lleva a la parada de la compilación.



Dependiendo de sus requisitos específicos, puede configurar el código de salida para que estas utilidades también detengan el proceso de compilación cuando detecten problemas de cierta gravedad. En nuestro caso, la compilación se detendrá solo si Trivy detecta una vulnerabilidad crítica que especificamos en la variable SHOWSTOPPER en .gitlab-ci.yml .





El resultado de la operación de cada utilidad se puede ver en el registro de cada tarea de escaneo, directamente en los archivos json en la sección de artefactos, o en un informe HTML simple (más sobre esto a continuación):





3. Para presentar los informes de la utilidad en una forma un poco más legible por humanos, se usa una pequeña secuencia de comandos de Python para convertir tres archivos json en un archivo HTML con una tabla de defectos.

Esta secuencia de comandos se inicia mediante una tarea de informe independiente y su artefacto final es un archivo HTML con un informe. La fuente del script también está en el repositorio y puedes adaptarla a tus necesidades, colores, etc.





Script de shell



La segunda opción es adecuada para los casos en los que necesita verificar las imágenes de Docker fuera del sistema CI / CD, o necesita tener todas las instrucciones en un formulario que se pueda ejecutar directamente en el host. Esta opción está cubierta por un script de shell listo para usar que se puede ejecutar en una máquina virtual limpia (o incluso real). El script sigue las mismas instrucciones que el gitlab-runner anterior.



Para que el script funcione correctamente, Docker debe estar instalado en el sistema y el usuario actual debe estar en el grupo de Docker.



El script en sí se puede tomar aquí: docker_sec_check.sh



Al comienzo del archivo, las variables se utilizan para establecer qué imagen se debe escanear y qué defectos críticos causarán la salida de la utilidad Trivy con el código de error especificado.



Durante la ejecución del script, todas las utilidades se descargarán en el directorio docker_tools , los resultados de su trabajo, en el directorio docker_tools / json , y el HTML con el informe estará en el archivo results.html .



Salida de script de muestra
~/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




Imagen de Docker con todas las utilidades



Como tercera alternativa, he compilado dos Dockerfiles simples para crear una imagen con utilidades de seguridad. Un Dockerfile ayudará a crear un conjunto para escanear una imagen desde el repositorio, el segundo (Dockerfile_tar): crear un conjunto para escanear un archivo tar con una imagen.



1. Tomamos el archivo Docker y los scripts correspondientes del repositorio https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile .

2. Ejecútelo para ensamblar:

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


3. Una vez finalizado el ensamblaje, cree un contenedor a partir de la imagen. Al mismo tiempo, pasamos la variable de entorno DOCKERIMAGE con el nombre de la imagen que nos interesa y montamos el Dockerfile que queremos analizar desde nuestra máquina al archivo / Dockerfile (tenga en cuenta que se requiere una ruta absoluta a este archivo):

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


resultados



Hemos cubierto solo un conjunto básico de herramientas para escanear artefactos de Docker, que, en mi opinión, cubre de manera bastante efectiva una parte decente de los requisitos de seguridad de la imagen. Hay muchas más herramientas gratuitas y de pago que pueden realizar las mismas comprobaciones, elaborar hermosos informes o trabajar únicamente en modo consola, cubrir sistemas de gestión de contenedores, etc. Una descripción general de estas herramientas y cómo integrarlas puede aparecer un poco más adelante.



El lado positivo del conjunto de herramientas, que se describe en el artículo, es que todas están creadas en código fuente abierto y puede experimentar con ellas y otras herramientas similares para encontrar lo que se adapte exactamente a sus requisitos y características de infraestructura. Por supuesto, todas las vulnerabilidades que se encontrarán deben estudiarse para su aplicabilidad en condiciones específicas, pero este es un tema para un futuro artículo extenso.



Espero que este tutorial, scripts y utilidades lo ayuden y se convierta en un punto de partida para crear una infraestructura más segura en el campo de la contenerización.



All Articles