Привет, меня зовут Егор и я 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
Pasted image 20240606140222.png
wikilink

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 страницы.

Pasted image 20240611083008.png
wikilink

Одно из самых важных преимуществ 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
Pasted image 20240611091115.png
wikilink

Для проверки спецификации 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
Pasted image 20240611092141.png
wikilink

Стоит учесть, что при проверке спецификации 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.

На этом все, спасибо за внимание, и добро пожаловать в комментарии!

Комментарии (2)


  1. Legendary8971
    26.12.2024 09:47

    Хороша ознакомительная статья, есть момент небольшой, который немного испортил впечатление:

    >Для простоты используем довольно простой проект.

    Очень большое количество ссылок в самой статье, как будто открыл пдф-брошуру Зетелькастена и начинаешь ходить по ссылкам. Очень удобно с учётом, что материал подается последовательно.


  1. PyLounge
    26.12.2024 09:47

    Такое нам надо. Спасибо