Привет, меня зовут Егор и я Tech Lead в компании ИдаПроджект :) Занимаюсь стратегией, процессами и командами в направлении backend разработки.
Сегодня расскажу вам о базовой настройке SAST и DAST для django в gitlab cicd. В разработке использование SAST (Static Application Security Testing) и DAST (Dynamic Application Security Testing) в последние годы стало уже стандартом. На эту тему есть уже довольно много материала на habr, но я хочу сконцентрироваться на быстром и базовом внедрении решения по безопасности в следующий стек технологий:
Infrastructure: Docker, Docker Compose, GitLab, GitLab CI/CD
Backend: Python, Django с использованием Poetry
Frontend: Vue.js, Nuxt.js
Погнали!
SAST (Static Application Security Testing)
Static Application Security Testing (SAST) — это методы статического анализа кода, которые помогают выявить потенциальные уязвимости на этапе разработки.
Мы будем использовать следующие инструменты:
bandit — утилита для проверки Python-кода на наличие распространенных уязвимостей.
trivy — швейцарский нож, который подходит для проверки Docker-контейнеров, git-репозиториев, операционных систем и исходного кода.
gitleaks — утилита для поиска паролей, хешей и других забытых чувствительных данных в коде.
Чем раньше вы обнаружите уязвимость, тем быстрее и легче ее будет исправить. Самый ранний этап для этого — локальная машина. Поэтому крайне важно установить pre-commit в вашем проекте.
В pre-commit можно интегрировать инструменты bandit и gitleaks. Они будут проверять исходный код на уязвимости при создании коммита на локальной машине и не позволят завершить коммит, если что-то обнаружат.
DAST (Dynamic Application Security Testing)
Dynamic Application Security Testing (DAST) — это метод тестирования безопасности, который осуществляется при выполнении приложения. В отличие от SAST, DAST анализирует работающее приложение, взаимодействуя с ним так, как это делал бы атакующий. Мы будем применять OWASP ZAP (Zed Attack Proxy).
Архитектура
Для простоты используем довольно легкий проект. Все исходники будут доступны в GitHub.
Итак, у нас есть следующая структура:
backend (Django + DRF)
frontend (Vue + Nuxt)
gitlab (CI/CD инструкции)
pre-commit-config.yml (конфигурация pre-commit)
docker-compose.yml (конфигурация для запуска всего проекта)
Весь проект можно поднять с помощью команды:
docker compose ‑f docker‑compose.yml up -‑build
GitLab CI/CD
Для начала настроим проверки для SAST решений в GitLab CI/CD. Разобьем инструкции CI/CD по файлам для удобства чтения.
default:
image: 'docker:23.0-dind'
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
stages:
- build
- security
- post-deploy
include:
- local: '/gitlab/build.yml'
- local: '/gitlab/security.yml'
- local: '/gitlab/post-deploy.yml'
gitleaks, bandit и pre-commit
Самое простое, быстрое и, что самое главное, полезное, мы можем сделать сразу же, то есть настроить pre-commit. Что такое pre-commit, и как его настроить, можно прочитать здесь. Из документации к gitleaks и bandit можем добавить hook’s, которые будут проверять наши файлы при коммите.
# файл .pre-commit-config.yaml
repos:
- repo: https://github.com/PyCQA/bandit
rev: '1.7.8'
hooks:
- id: bandit
args: ["-c", "backend/pyproject.toml"]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.3
hooks:
- id: gitleaks
Тут стоит подсветить кастомную конфигурацию у bandit, а именно, строку args: ["-c", "backend/pyproject.toml"]. Здесь мы передаем файл pyproject.toml, который обычно является конфигурационным файлом для всего python приложения. Про кастомную конфигурацию утилиты bandit можно подробнее почитать здесь.
Далее выполняем команды:
pre-commit install
pre-commit run
gitleaks, bandit в GitLab CI/CD
pre-commit — это, конечно прекрасно, но иногда разработчики не ставят его на локальном устройстве. Поэтому добавление gitleaks и bandit в CI/CD лишним не будет. Мы определили отдельный stage «security», в котором и будут происходить проверка на безопасность наших образов.
Файл gitlab/security.yml будет иметь такое содержание:
gitleaks:
stage: security
before_script: [ ]
image:
name: "zricethezav/gitleaks"
entrypoint: [ "" ]
script:
- gitleaks detect -v ./
allow_failure: true
bandit:
stage: security
before_script: [ ]
image:
name: "ghcr.io/pycqa/bandit/bandit"
entrypoint: [ "" ]
script:
- gitleaks detect -v ./
allow_failure: true
Локальный запуск trivy
Trivy можно запустить как на локальной машине, так и в CI/CD. Он отлично подходит для проверки исходного кода или Docker-образов. Пример проверки исходного кода:
docker run -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}:/path aquasec/trivy:0.52.0 fs
Пример проверки локального образа:
docker run -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}:/path aquasec/trivy:0.52.0 image sast-and-dast-for-gitlab-backend
trivy в GitLab CI/CD
Дополним файл gitlab/security.yml следующим содержанием:
Проверка backend образа:
trivy:backend:
stage: security
before_script: [ ]
image:
name: docker.io/aquasec/trivy:latest
entrypoint: [ "" ]
variables:
GIT_STRATEGY: none
TRIVY_USERNAME: "$CI_REGISTRY_USER" # Логин для скачивания образа из registry
TRIVY_PASSWORD: "$CI_REGISTRY_PASSWORD" # Пароль для скачивания образа из registry
TRIVY_AUTH_URL: "$CI_REGISTRY" # Пароль для скачивания образа из registry
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
script:
- time trivy image --clear-cache
- time trivy image --download-db-only
- time trivy image --exit-code 1 --severity CRITICAL "${CI_REGISTRY_IMAGE}/backend:${CI_COMMIT_REF_NAME}"
cache:
paths:
- .trivycache/
allow_failure: true
Проверка frontend образа:
trivy:frontend:
stage: security
before_script: [ ]
image:
name: docker.io/aquasec/trivy:latest
entrypoint: [ "" ]
variables:
GIT_STRATEGY: none
TRIVY_USERNAME: "$CI_REGISTRY_USER"
TRIVY_PASSWORD: "$CI_REGISTRY_PASSWORD"
TRIVY_AUTH_URL: "$CI_REGISTRY"
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
script:
- time trivy image --clear-cache
- time trivy image --download-db-only
- time trivy image --exit-code 1 --severity CRITICAL "${CI_REGISTRY_IMAGE}/frontend:${CI_COMMIT_REF_NAME}"
cache:
paths:
- .trivycache/
allow_failure: true
Проверка всего репозитория на наличие уязвимостей:
trivy:repository:
stage: security
before_script: [ ]
image:
name: docker.io/aquasec/trivy:latest
entrypoint: [ "" ]
variables:
TRIVY_USERNAME: "$CI_REGISTRY_USER"
TRIVY_PASSWORD: "$CI_REGISTRY_PASSWORD"
TRIVY_AUTH_URL: "$CI_REGISTRY"
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
script:
- time trivy image --clear-cache
- time trivy fs --exit-code 1 --severity CRITICAL ./
cache:
paths:
- .trivycache/
allow_failure: true
Для задачи trivy:repository (из файла выше) стоит сделать уточнение. Обычно возникает вопрос: зачем нам проверять репозиторий с исходным кодом, если мы и так это делаем со всеми контейнерами? Ответ: в образ приложения могут не попасть некоторые файлы, которые используются при многостадийной сборке, но они также участвуют в сборке образа, и соответственно, могут реализовать уязвимость на этом шаге. Поэтому надежнее использовать разные методы проверки, особенно в рамках одного инструмента.
OWASP ZAP (Zed Attack Proxy)
OWASP ZAP — один из самых популярных инструментов для DAST анализа. Он позволяет реализовать поиск по самым популярным уязвимостям, но его можно настроить и под конкретные нужды.
В этом примере мы сделаем базовый вариант автоматической проверки работающего приложения. Сначала сделаем это локально, а потом автоматизируем с помощью GitLab CI/CD.
Локальный запуск ZAP
Если у вас уже развернуто web приложение, вы можете сделать первичный и самый простой, анализ уязвимостей. Используем команду:
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:latest zap-baseline.py -t https://habr.com -r report.html
В консоли будут выведены все обнаруженные уязвимости, но их неудобно читать.
WARN-NEW: Session Management Response Identified [10112] x 7
https://habr.com/en/flows/develop/ (302 Found)
https://habr.com/kek/v1/auth/habrahabr-register/?back=/en/search/&hl=en (302 Found)
https://habr.com/kek/v1/auth/habrahabr-register/?back=/ru/search/&hl=ru (302 Found)
https://habr.com/kek/v1/auth/habrahabr-register/?back=/ru/sitemap.xml/&hl=ru (302 Found)
https://habr.com/kek/v1/auth/habrahabr/?back=/en/search/&hl=en (302 Found)
WARN-NEW: Absence of Anti-CSRF Tokens [10202] x 5
https://habr.com (200 OK)
https://habr.com/en/feed/ (200 OK)
https://habr.com/en/search/ (200 OK)
https://habr.com/ru/feed/ (200 OK)
https://habr.com/ru/search/ (200 OK)
WARN-NEW: Sub Resource Integrity Attribute Missing [90003] x 45
https://habr.com/ru/sitemap.xml/ (404 Not Found)
https://habr.com/ru/sitemap.xml/ (404 Not Found)
https://habr.com/ru/sitemap.xml/ (404 Not Found)
https://habr.com/ru/sitemap.xml/ (404 Not Found)
https://habr.com/ru/sitemap.xml/ (404 Not Found)
FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 17 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 48
Чтобы сделать информацию более понятной, лучше использовать функционал от отчетов. Сейчас мы использовали -r report.html, что позволило нам получить отчет в виде единой HTML страницы.
Одно из самых важных преимуществ HTML отчета — каждая уязвимость имеет полное описание, пример и расписанное решение. Такие отчеты хорошо подходят менеджерам, которые хотят верхнеуровнево понять, что происходит с проектом, какие есть уязвимости и их критичность.
Проверка API OWASP
ZAP также умеет проверять API эндпоинты. Это может быть спецификация OpenAPI, REST API, GraphQL и даже SOAP.
Локально проверку для REST API можно запустить следующим образом:
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:latest zap-baseline.py -t https://api.github.com/ -r report.html
Для проверки спецификации OpenAPI можно запустить следующий скрипт:
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:latest zap-api-scan.py -t https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json -f openapi -r report.html
Стоит учесть, что при проверке спецификации OpenAPI OWASP ZAP сам найдет и создаст все возможные URL для проверки, и будет проходиться по каждому из них. Кроме того, если указан POST-запрос с формой, он попытается вставить туда тестовые данные, поэтому если вы проверяете на боевой версии API, нужно иметь это в виду.
Более подробно про то, как OWASP ZAP проверяет API, можно почитать здесь.
OWASP ZAP в GitLab CI/CD
Для внедрения OWASP ZAP в GitLab CI/CD можно использовать разные подходы. В данной статье реализуем простое добавление инструкций в CI/CD в виде отдельных задач. Для этого сделаем отдельную стадию в CI/CD, под названием post-deploy.
Важно: поскольку для OWASP ZAP требуется работающее приложение, нужно делать проверку после деплоя — и желательно с задержкой по времени (например, пять минут).
zap:site:
stage: post-deploy
before_script: [ ]
image:
name: ghcr.io/zaproxy/zaproxy:latest
entrypoint: [ "" ]
variables:
ZAP_REPORT_DIR: /zap/wrk/
ZAP_REPORT: report_site.html
script:
- mkdir -p ${ZAP_REPORT_DIR}
- zap-baseline.py -t https://idaproject.com -r ${ZAP_REPORT} || true
- cp ${ZAP_REPORT_DIR}${ZAP_REPORT} ${ZAP_REPORT}
artifacts:
when: always
expire_in: 1 week
paths:
- ${ZAP_REPORT}
zap:api:
stage: post-deploy
before_script: [ ]
image:
name: ghcr.io/zaproxy/zaproxy:latest
entrypoint: [ "" ]
variables:
ZAP_REPORT_DIR: /zap/wrk/
ZAP_REPORT: report_api.html
script:
- mkdir -p ${ZAP_REPORT_DIR}
- zap-api-scan.py -t https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json -f openapi -r ${ZAP_REPORT} || true
- cp ${ZAP_REPORT_DIR}${ZAP_REPORT} ${ZAP_REPORT}
artifacts:
when: always
expire_in: 1 week
paths:
- ${ZAP_REPORT}
Этот подход можно усовершенствовать с помощью подготовки контекстов, в которые можно вложить авторизацию и использование дополнительных модулей. Подробнее можно прочитать в этой статье на Хабре.
Заключение
Внедрение решений по безопасности с использованием SAST и DAST — важный шаг к защите вашего приложения. В этой статье мы рассмотрели базовые принципы настройки и использования инструментов Bandit, Gitleaks, Trivy и OWASP ZAP — как на локальной машине, так и в рамках CI/CD.
Следующим вашим шагом должно стать углубленное изучение каждого инструмента и его возможностей. Поэтому рекомендую прочитать документацию по каждому инструменту, и сравнить разные подходы к внедрению решений по безопасности, например:
Внедряем DevSecOps в процесс разработки. Часть 1. Обзор инструментов, Pre-commit Checks
Как превратить DevOps-пайплайн в DevSecOps-пайплайн. Обзор концепции Shift Left
Ну и, конечно, еще раз добавлю ссылку на репозиторий с кодом на GitHub.
На этом все, спасибо за внимание, и добро пожаловать в комментарии!
Legendary8971
Хороша ознакомительная статья, есть момент небольшой, который немного испортил впечатление:
>Для простоты используем довольно простой проект.
Очень большое количество ссылок в самой статье, как будто открыл пдф-брошуру Зетелькастена и начинаешь ходить по ссылкам. Очень удобно с учётом, что материал подается последовательно.