В данной статье я поделюсь готовыми сценариями для реализации нескольких утилит безопасности Docker и инструкциями по развертыванию небольшого демонстрационного стенда для тестирования этого процесса. Вы можете использовать ресурсы, чтобы поэкспериментировать с тем, как организовать процесс тестирования безопасности для образов и инструкций Dockerfile. Понятно, что инфраструктура разработки и внедрения у всех разная, поэтому ниже я приведу несколько возможных вариантов.
Утилиты проверки безопасности
Имеется масса различных вспомогательных приложений и скриптов, которые тестируют различные аспекты инфраструктуры Docker. В этом материале я хотел бы остановиться на трех из них, которые покрывают часть основных требований безопасности для образов Docker, созданных во время разработки. Кроме того, я также покажу пример того, как эти три утилиты могут быть связаны в конвейер для выполнения проверок безопасности.
Dockle
https://github.com/goodwithtech/dockle
Консольная утилита, которая работает с образом (или tar-файлом, сохраненным из образа), которая проверяет точность и безопасность конкретного образа как такового, анализируя его слои и конфигурацию — какие пользователи созданы, какие инструкции используются, какие тома монтируются, наличие пустого пароля и т. д. Хотя количество проверок не очень велико и основано на нескольких наших собственных проверках и рекомендациях CIS (Center for Internet Security) Benchmark for Docker.
Hadolint
https://github.com/hadolint/hadolint
Достаточно простая консольная утилита, помогающая в первом приближении оценить правильность и безопасность инструкций Dockerfile (например, с использованием только авторизованных регистров образов или с помощью sudo).
Trivy
https://github.com/aquasecurity/trivy
Эта утилита направлена на обнаружение двух типов уязвимостей безопасности: проблемы сборки операционных систем (поддерживаемые Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) и проблемы зависимостей (Gemfile.lock, Pipfile.lock, composer.lock, package- lock) .json, yarn.lock, Cargo.lock). Trivy может сканировать как образ в репозитории, так и локальный образ, а также сканировать с помощью образа Docker на основе переданного файла .tar.
Способы внедрения утилит
Чтобы опробовать описанные приложения в изолированных условиях, я предоставлю инструкции по установке всех утилит в упрощенном режиме.
Основная идея — продемонстрировать, как можно реализовать автоматическую проверку содержимого для Dockerfile и образов Docker, созданных во время разработки.
Сама проверка состоит из следующих шагов:
- Проверка корректности и безопасности инструкций Dockerfile — утилитой линтером Hadolint
- Контроль корректности и безопасности конечного и промежуточных образов — утилитой Dockle
- Проверка наличия общеизвестных уязвимостей (CVE) в базовом образе и ряде зависимостей — утилитой Trivy
Позже в статье я предложу три варианта реализации этих шагов:
Первый — настроить конвейер CI / CD на примере GitLab (с описанием процесса генерации тестового экземпляра).
Второй использует сценарий оболочки.
Третий включает создание образа Docker для сканирования образов Docker.
Вы можете выбрать наиболее подходящий вариант, перенести его в свою инфраструктуру и адаптировать под свои нужды.
Все необходимые файлы и дополнительные инструкции также находятся в репозитории: https://github.com/Swordfish-Security/docker_cicd
Интеграция в GitLab CI/CD
В первом варианте мы рассмотрим, как можно реализовать проверки безопасности на примере системы репозитория GitLab. Здесь мы рассмотрим шаги и выясним, как настроить тестовую среду с GitLab с нуля, создать процесс сканирования и запустить утилиты для проверки тестового файла Docker и случайного образа — приложения JuiceShop.
Установка GitLab
1. Ставим Docker:
sudo apt-get update && sudo apt-get install docker.io
2. Добавляем текущего пользователя в группу docker, чтобы можно было работать с докером не через sudo:
sudo addgroup <username> docker
3. Находим свой IP:
ip addr
4. Ставим и запускаем GitLab в контейнере, заменяя IP адрес в hostname на свой:
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
Ждём, пока GitLab выполнит все необходимые процедуры по установке (можно следить за процессом через вывод лог-файла: docker logs -f gitlab).
5. Открываем в браузере свой локальный IP и видим страницу с предложением поменять пароль для пользователя root:
Задаём новый пароль и заходим в GitLab.
6. Создаём новый проект, например cicd-test и инициализируем его стартовым файлом README.md:
7. Теперь нам необходимо установить GitLab Runner: агента, который будет по запросу запускать все необходимые операции.
Скачиваем последнюю версию (в данном случае — под Linux 64-bit):
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
8. Делаем его исполняемым:
sudo chmod +x /usr/local/bin/gitlab-runner
9. Добавляем пользователя ОС для Runner-а и запускаем сервис:
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
Должно получиться примерно так:
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. Теперь регистрируем Runner, чтобы он мог взаимодействовать с нашим инстансом GitLab.
Для этого открываем страницу Settings-CI/CD (http://OUR_ IP_ADDRESS/root/cicd-test/-/settings/ci_cd) и на вкладке Runners находим URL и Registration token:
11. Регистрируем Runner, подставляя URL и Registration token:
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"
В результате мы получаем готовый GitLab, в который нам нужно добавить инструкции по запуску наших утилит. В этом демонстрационном случае у нас нет шагов по созданию приложения и его контейнеризации, но в реальной среде они будут предшествовать шагам сканирования и генерировать образы и Dockerfile для анализа.
Конфигурация pipeline
1. Добавьте файлы mydockerfile.df в репозиторий (это тестовый файл Docker, который мы проверим) и файл конфигурации процесса GitLab CI / CD .gitlab-cicd.yml, содержащий инструкции для сканеров (обратите внимание на точку в названии файла).
YAML-файл конфигурации содержит инструкции по запуску трех утилит (Hadolint, Dockle и Trivy), которые проанализируют выбранный Dockerfile и образ, заданный в переменной DOCKERFILE. Все необходимые файлы можно взять из репозитория: https://github.com/Swordfish-Security/docker_cicd/
Выдержка из mydockerfile.df (это абстрактный файл с набором произвольных инструкций только для демонстрации работы утилиты). Прямая ссылка на файл: mydockerfile.df
Содержимое 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
Конфигурационный YAML выглядит таким образом (сам файл можно взять по прямой ссылке здесь: .gitlab-ci.yml):
Содержимое .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
При необходимости также можно сканировать и сохраненные образы в виде .tar-архива (однако потребуется в YAML файле изменить входные параметры для утилит)
NB: Для работы Trivy необходимы установленные rpm и git. В противном случае он будет генерировать ошибки при сканировании образов на основе RedHat и получении обновлений для базы данных уязвимостей.
2. После добавления файлов в репозиторий, следуя инструкциям в нашем файле конфигурации, GitLab автоматически запустит процесс сборки и сканирования. На вкладке CI / CD → Pipelines вы можете увидеть, как выполняются инструкции.
В результате у нас есть четыре задачи. Три из них занимаются непосредственно сканированием и последняя (Report) собирает простой отчёт из разрозненных файлов с результатами сканирования.
По умолчанию Trivy перестает работать, если в образе или зависимостях обнаруживаются критические уязвимости безопасности. При этом Hadolint всегда возвращает коду выполнения Success, потому что в результате его выполнения всегда есть комментарии, останавливающие компиляцию.
В зависимости от ваших конкретных требований вы можете настроить код выхода так, чтобы эти утилиты также останавливали процесс сборки при обнаружении проблем определенной степени серьезности. В нашем случае сборка остановится, только если Trivy обнаружит критическую уязвимость, указанную в переменной SHOWSTOPPER в .gitlab-ci.yml.
Результат работы каждой утилиты можно посмотреть в логе каждой сканирующей задачи, непосредственно в json-файлах в разделе artifacts или в простом HTML-отчёте (о нем чуть ниже):
3. Чтобы представить отчеты утилиты в более удобочитаемой форме, используется небольшой скрипт Python для преобразования трех файлов json в файл HTML с таблицей дефектов.
Этот скрипт запускается из отдельной задачи Report, и его последний артефакт — это HTML-файл с отчетом. Исходный код скрипта также находится в репозитории, и вы можете адаптировать его под свои нужды, цвета и т. д.
Shell-скрипт
Вариант №2 подходит для случаев, когда вам нужно проверить образы Docker вне системы CI / CD или вам нужно иметь все инструкции в формате, который может быть выполнен непосредственно на хосте. Этот вариант покрывается готовым сценарием оболочки, который можно запустить на чистой виртуальной (или даже реальной) машине. Скрипт следует тем же инструкциям, что и gitlab-runner выше.
Для успешной работы скрипта в системе должен быть установлен Docker и текущий пользователь должен быть в группе docker.
Сам скрипт можно взять здесь: docker_sec_check.sh
В начале файла используются переменные, чтобы определить, какой образ сканировать, и дефекты, серьезность которых приведет к завершению работы утилиты Trivy с указанным кодом ошибки.
Во время выполнения скрипта все утилиты будут загружены в каталог docker_tools, результаты вашей работы — в каталог docker_tools / json, а HTML с отчетом будет в файле results.html.
Пример вывода скрипта
~/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
Docker-образ со всеми утилитами
В качестве третьей альтернативы я скомпилировал два простых файла Docker для создания образа с помощью утилит безопасности. Один Dockerfile поможет собрать набор для сканирования образа из репозитория, второй (Dockerfile_tar) — собрать набор для сканирования tar-файла с образом.
1. Берем соответствующий Docker файл и скрипты из репозитория https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile.
2. Запускаем его на сборку:
docker build -t dscan:image -f docker_security.df .
3. Когда сборка будет завершена, создайте контейнер из образа. В то же время мы передаем переменную окружения DOCKERIMAGE с именем интересующего нас образа и прикрепляем файл Docker, который мы хотим проанализировать с нашего компьютера, на файл /Dockerfile (обратите внимание, что что требуется абсолютный путь до этого файла):
:
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
Заключение
Позитивной стороной набора инструментария, описанного в статье, является то, что все они построены на открытом исходном коде, и вы можете экспериментировать с ними и другими подобными инструментами, чтобы найти то, что именно соответствует вашим требованиям и возможностям инфраструктуры. Конечно, все уязвимости, которые будут обнаружены, должны быть изучены на предмет применимости в конкретных условиях, но это тема для будущей большой статьи.Мы проанализировали только один базовый набор инструментов сканирования артефактов Docker, который, на мой взгляд, достаточно эффективно покрывает значительную часть требований безопасности для образов. Есть много других бесплатных и платных инструментов, которые могут выполнять те же проверки, создавать отчеты или работать только в режиме консоли, включая системы управления контейнерами и т. д. Обзор этих инструментов и способов их интеграции может появиться немного позже.