Способи та приклади впровадження утиліт для перевірки безпеки Docker

Способи та приклади впровадження утиліт для перевірки безпеки Docker
Привіт, Хабре!

У сучасній реальності через зростання ролі контейнеризації в процесах розробки не на останньому місці стоїть питання забезпечення безпеки різних етапів і сутностей, пов'язаних з контейнерами. Здійснення перевірок у ручному режимі є трудомістким заняттям, тому було б непогано зробити хоча б початкові кроки до автоматизації цього процесу.

У цій статті я поділюся готовими скриптами для впровадження кількох утиліт забезпечення безпеки Docker та інструкцією, як розгорнути невеликий демо-стенд для перевірки цього процесу. Матеріалами можна скористатися, щоб поекспериментувати, як організувати процес тестування безпеки образів та інструкцій Dockerfile. Зрозуміло, що інфраструктура розробки та впровадження у всіх є різні, тому нижче я наведу кілька можливих варіантів.

Утиліти перевірки безпеки

Існує велика кількість різних допоміжних програм та скриптів, які виконують перевірки різноманітних аспектів Docker-інфраструктури. Частина з них вже була описана у попередній статті (https://habr.com/ru/company/swordfish_security/blog/518758/#docker-security), а в даному матеріалі я хотів би зупинитися на трьох з них, які покривають основну частину вимог до безпеки Docker-образів, що будуються в процесі розробки. Крім цього, я також покажу приклад як можна ці три утиліти з'єднати в один pipeline для здійснення перевірок безпеки.

Hadolint
https://github.com/hadolint/hadolint

Досить проста консольна утиліта, яка допомагає в першому наближенні оцінити коректність та безпеку інструкцій Dockerfile-ів (наприклад, використання лише дозволених реєстрів образів або використання sudo).

Способи та приклади впровадження утиліт для перевірки безпеки Docker

Докль
https://github.com/goodwithtech/dockle

Консольна утиліта, що працює з образом (або зі збереженим tar-архівом образу), яка перевіряє коректність та безпеку конкретного образу як такого, аналізуючи його шари та конфігурацію – які користувачі створені, які інструкції використовуються, які томи підключені, присутність порожнього пароля тощо. д. Поки кількість перевірок не дуже велика і базується на кількох власних перевірках та рекомендаціях CIS (Center for Internet Security) Benchmark для Docker.
Способи та приклади впровадження утиліт для перевірки безпеки Docker

Дрібний
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 вміє сканувати як образ у репозиторії, так і локальний образ, а також проводити сканування на підставі переданого файлу .tar з Docker-образом.

Способи та приклади впровадження утиліт для перевірки безпеки Docker

Варіанти впровадження утиліт

Для того, щоб спробувати в ізольованих умовах описані програми, я наведу інструкції зі встановлення всіх утиліт у рамках деякого спрощеного процесу.

Основна ідея полягає в тому, щоб продемонструвати, як можна впровадити автоматичну перевірку вмісту Dockerfile та Docker-образів, які створюються в процесі розробки.

Сама перевірка складається з наступних кроків:

  1. Перевірка коректності та безпеки інструкцій Dockerfile – утилітою лінтером Hadolint
  2. Перевірка коректності та безпеки кінцевого та проміжних образів – утилітою Докль
  3. Перевірка наявності загальновідомих уразливостей (CVE) у базовому образі та ряді залежностей – утилітою Дрібний

Далі у статті я наведу три варіанти впровадження цих кроків:
Перший - шляхом конфігурації CI/CD pipeline на прикладі GitLab (з описом процесу підняття тестового інстансу).
Другий – з використанням shell-скрипту.
Третій — із побудовою Docker-образу для сканування Docker-образів.
Ви можете вибрати варіант, який більше вам підходить, перенести його на свою інфраструктуру та адаптувати під свої потреби.

Усі необхідні файли та додаткові інструкції також знаходяться у репозиторії: https://github.com/Swordfish-Security/docker_cicd

Інтеграція в GitLab CI/CD

У першому варіанті ми розглянемо, як можна впровадити перевірки безпеки з прикладу системи репозиторіїв GitLab. Тут ми пройдемо кроками і розберемо як встановити з нуля тестове оточення з GitLab, скласти процес сканування і здійснити запуск утиліт для перевірки тестового Dockerfile і випадкового образу - програми 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:
Способи та приклади впровадження утиліт для перевірки безпеки Docker
Задаємо новий пароль і заходимо до GitLab.

6. Створюємо новий проект, наприклад cicd-test та ініціалізуємо його стартовим файлом README.md:
Способи та приклади впровадження утиліт для перевірки безпеки Docker
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:
Способи та приклади впровадження утиліт для перевірки безпеки Docker
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 (це тестовий Dockerfile, який ми перевірятимемо) і конфігураційний файл 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 <[email protected]>"
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 вимагає для свого запуску встановлені оборотів в хвилину и мерзотник. В іншому випадку він видаватиме помилки при скануванні RedHat-based образів та отриманні оновлень бази вразливостей.

2. Після додавання файлів до репозиторій, відповідно до інструкцій у нашому конфігураційному файлі, GitLab автоматично розпочне процес складання та сканування. На вкладці CI/CD → Pipelines можна побачити хід виконання інструкцій.

У результаті ми маємо чотири завдання. Три з них безпосередньо займаються скануванням і остання (Report) збирає простий звіт з розрізнених файлів з результатами сканування.
Способи та приклади впровадження утиліт для перевірки безпеки Docker
За замовчуванням Trivy зупиняє своє виконання, якщо було виявлено CRITICAL вразливості у образі чи залежностях. У той самий час Hadolint завжди повертає Success код виконання, оскільки у його виконання завжди є зауваження, що призводить до зупинки складання.

Залежно від конкретних вимог можна налаштувати код виходу, щоб ці утиліти при виявленні проблем певної критичності, зупиняли також процес складання. У нашому випадку збірка зупиниться, тільки якщо Trivy виявить вразливість із критичністю, яку ми вказали у змінній SHOWSTOPPER у .gitlab-ci.yml.
Способи та приклади впровадження утиліт для перевірки безпеки Docker

Результат роботи кожної утиліти можна подивитися в лозі кожного скануючого завдання, безпосередньо в json-файлах в розділі artifacts або в простому HTML-звіті (про нього трохи нижче):
Способи та приклади впровадження утиліт для перевірки безпеки Docker

3. Для подання звітів утиліт у трохи більше людиночитаному вигляді використовується невеликий скрипт на Python для конвертації трьох json-файлів в один HTML-файл з таблицею дефектів.
Цей скрипт запускається окремим завданням Report, а його підсумковим артефактом є HTML-файл зі звітом. Вихідник скрипту також лежить у репозиторії та його можна адаптувати під свої потреби, кольори тощо.
Способи та приклади впровадження утиліт для перевірки безпеки Docker

Shell-скрипт

Другий варіант підходить для випадків, коли необхідно перевіряти Docker образи не в рамках CI/CD системи або необхідно мати всі інструкції у вигляді, який можна виконати безпосередньо на хості. Цей варіант покривається готовим shell-скриптом, який можна запустити чистою віртуальною (або навіть реальною) машиною. Скрипт виконує ті ж інструкції, що і вищеописаний gitlab-runner.

Для успішної роботи скрипта в системі повинен бути встановлений Docker, і поточний користувач повинен бути в групі docker.

Сам скрипт можна взяти тут: docker_sec_check.sh

На початку файлу змінними задається, який образ необхідно сканувати і дефекти якої критичності викликатимуть вихід із утиліти Trivy із зазначеним кодом помилки.

У процесі виконання скрипту всі утиліти будуть завантажені в директорію docker_tools, результати їх роботи - у директорію docker_tools/json, а HTML зі звітом перебуватиме у файлі результати.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-образ з усіма утилітами

Як третя альтернатива я склав два простих Dockerfile для створення образу з утилітами безпеки. Один 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 з назвою образу, що цікавить нас, і монтуємо Dockerfile, який хочемо аналізувати, з нашої машини на файл /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, який, як на мене, дуже ефективно покриває пристойну частину вимог до безпеки образів. Існує ще велика кількість платних та безкоштовних інструментів, які можуть виконувати ті ж перевірки, малювати гарні звіти або працювати чисто в консольному режимі, охоплювати системи керування контейнерами тощо. Огляд цих інструментів та способів їх інтеграції, можливо, з'явиться трохи згодом.

Позитивною стороною набору інструментів, який описаний у статті, є те, що всі вони побудовані на відкритому вихідному коді і ви можете експериментувати з ними та іншими подібними інструментами, щоб знайти те, що саме підходить під ваші вимоги та особливості інфраструктури. Безумовно, всі вразливості, які будуть знайдені, повинні бути вивчені на застосовність у конкретних умовах, але це тема для майбутньої великої статті.

Сподіваюся, ця інструкція, скрипти та утиліти допоможуть вам і стануть відправною точкою для створення безпечнішої інфраструктури в області, що стосується контейнеризації.

Джерело: habr.com

Додати коментар або відгук