В прошлой части цикла мы подготовили фундамент будущего РБПО: развернули виртуальные машины, настроили Ubuntu Server, сеть, брандмауэр и Docker. Другими словами, построили площадку, на которой теперь можно начинать возводить сам конвейер безопасной разработки.

Теперь пора наполнять наши виртуальные машины полезным содержимым. Если продолжать сказочную аналогию — заселим наше царство безопасной разработки первыми жителями: хранителем секретов, смотрителем артефактов, летописцем уязвимостей и прочими полезными персонажами. То есть установим и настроим GitLab, Nexus, HashiCorp Vault, DefectDojo и Dependency-Track, а также подготовим отдельную виртуальную машину с набором CLI-инструментов для анализа безопасности.

Большая часть сервисов будет работать в Docker-контейнерах, поэтому заодно разберемся с настройкой постоянного хранения данных, Docker Compose и некоторыми особенностями конфигурации отдельных компонентов.

Что ж, глаза боятся, а руки команды в терминале набирают. Начнем с GitLab.

Дисклеймер

Важно сразу условиться, Путник!

Данный цикл статей не заменяет требования ГОСТ, OWASP SAMM, BSIMM и других серьезных методологий безопасной разработки. Это скорее практический пример того, как можно организовать конвейер безопасной разработки ПО с минимальным вложением средств. 

Всё описанное в цикле — исключительно мой личный опыт, набитые шишки и результаты долгих вечеров за клавиатурой. Это не официальная позиция моего текущего или бывших работодателей. 

Воспринимать статью как руководство к бездумному копированию тоже не стоит. Проверяй всё на тестовых стендах, делай бэкапы и подходи к экспериментам с холодной головой.

Ошибки возможны, и не нужно бояться их совершать. Главное — делать выводы и постепенно строить процессы, которые будут надежнее вчерашних. 


GitLab

«GitLab — наше всё», — подумал я и решил: ставим Community Edition, потому что денег на платную версию у царя стартаподелающего нет (да и у меня тоже). К счастью, GitLab CE ставится чуть ли не одной командой.

Сначала добавим официальный репозиторий GitLab, чтобы наш пакетный менеджер знал, откуда качать:

curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash

Потом одной командой установим пакет:

sudo apt install -y gitlab-ce

После установки нужно указать адрес, по которому будет доступен GitLab. Для этого редактируем конфиг /etc/gitlab/gitlab.rb. Нужно указать внешний URL (можно использовать IP или домен, но на сейчас я оставлю http://192.168.0.201).

sudo nano /etc/gitlab/gitlab.rb

Ищем строчку external_url и правим ее, вписывая свой IP. Порт 8080 использовать не стоит — его в нашем стенде занимает встроенный сервер Puma.

external_url ‘http:// 192.168.0.201:9090’;

Применяем конфигурацию (нужно будет немного подождать):

sudo gitlab-ctl reconfigure

Проверяем статус, чтобы увидеть весь список запущенных сервисов:

sudo gitlab-ctl status

После завершения GitLab будет доступен по адресу http://192.168.0.201:9090.

Пробуем войти. Логин и пароль по умолчанию:

  • Логин — root; 

  • Пароль — временный пароль, сгенерированный при установке, хранится на виртуальной машине. Доступен по команде:

sudo cat /etc/gitlab/initial_root_password
Рисунок 1. Главная страница GitLab
Рисунок 1. Главная страница GitLab

Nexus

Привычную загрузку дистрибутива Nexus через wgetс официального сайта затруднительно реализовать в силу блокировок, поэтому я решил пойти другим путем — развернуть Nexus в Docker. Чтобы данные не потерялись при пересоздании контейнера, сразу настроим постоянное хранилище на хосте. 

Да-да, знаю, некоторые скажут: «Небезопасно, ох небезопасно!». Но если хакер уже залез на виртуалку, то ему всё равно, где данные лежат — в контейнере или рядом. А вот в условиях нашего лабораторного стенда такой подход вполне оправдан. Поэтому создаем файловую систему, подготавливаем папки и запускаем docker-compose.

Сначала создадим директорию для данных Nexus и дадим правильные права пользователю с UID 200 — это внутренний пользователь контейнера. Затем напишем docker-compose.yml с пробросом порта 8081 и примонтируем том. Запустим — и готово.

Создаю папку для данных:

sudo mkdir -p /opt/nexus-data

Важно! Nexus внутри контейнера работает от пользователя с UID 200. Нужно дать права на папку этому пользователю.

sudo chown -R 200:200 /opt/nexus-data

Создаю файл docker-compose.yml:

version: '3'
services:
  nexus:
    image: sonatype/nexus3:latest
    container_name: nexus
    restart: unless-stopped
    ports:
      - "8081:8081"
    volumes:
      - nexus-data:/nexus-data
volumes:
  nexus-data:
    # Указываю конкретную папку:
    driver_opts:
      type: none
      device: /opt/nexus-data
      o: bind

Запускаю Nexus командой:

docker-compose up -d

Проверяю работу в браузере по адресу http://192.168.0.202:8081/.

Получаю окно логина. Ура, у нас есть хранилище артефактов и хранилище внешних зависимостей!

Рисунок 2. Главная страница Nexus
Рисунок 2. Главная страница Nexus

Теперь получаю пароль для входа в сервис:

 docker exec nexus cat /nexus-data/admin.password

Проверка сохранения данных. Меняю пароль для входа в сервис. Торможу сервис и удаляю контейнер командами:

docker stop nexus
docker rm nexus

Запускаю docker-compose:

docker-compose up -d

Логинюсь с тем паролем, на который я заменил стандартный. Если он подходит, значит, хранилище смонтировано корректно.

Всё работает! Огонь, погнали дальше.

HashiCorp Vault

В любом уважающем себя царстве есть сокровищница. В нашем стенде ее роль будет выполнять HashiCorp Vault, где будут храниться пароли, токены и другие секреты.

Как и в случае с Nexus, для развертывания будем использовать docker-compose и постоянное хранилище на хосте. Надо же когда-то привыкать к контейнеризации.

Для начала подготовим структуру каталогов и конфигурацию Vault. Для лабораторного стенда я выбрал хранилище типа file: кластерный режим мне пока не нужен, а ресурсы хочется расходовать экономно.

Но помните! Для продакшена берите raft, как в табличке ниже. 

Немного информации:

  • Используйте file, если вы разворачиваете Vault для ознакомления, разработки или в сценариях, где допустимы простои и не требуется масштабирование.

  • Используйтеraft для production-окружений, где важна непрерывность работы. Даже если у вас только один узел, raft можно настроить в режиме одного сервера, при этом вы получите возможность легко добавить узлы в будущем.

Тип хранилища

file

raft

Режим работы

Только один узел

Кластер (3+ узлов)

Высокая доступность

Нет

Да (автоматическая смена лидера)

Репликация данных

Отсутствует

Полная репликация между узлами

Требования к инфраструктуре

Локальный диск

Ничего внешнего (кроме сети между узлами)

Простота настройки

Очень простая

Требуется конфигурация кластера

Использование в продакшене

Не рекомендуется

Рекомендуется

Пример конфигурации

storage "file" { path = "/vault/data" }

storage "raft" { path = "/vault/raft" node_id = "node1" }

Создаю директорию ~/vault, внутри — папки для данных, логов, сертификатов. Пишу простой конфиг с файловым хранилищем. Затем поднимаю контейнер через docker-compose и выполня. инициализацию Vault внутри контейнера. Не забудьте сохранить ключи распечатки и root-токен!

Создаю следующую структуру директорий и раздаю права:

mkdir -p ~/vault/
cd ~/vault
mkdir -p data logs certs init-scripts
cd data

Выдаю права:

sudo chmod 755 data logs certs init-scripts
sudo chown -R 100:1000 data logs

Для домашней лаборатории буду использовать хранилище типа file.

Создаю файл конфигурации Vault /vault/config/config.hcl:

storage "file" {
  path = "/vault/data"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = true
}

ui           = true
disable_mlock = true   # обязательно для работы в контейнере

Создаю файл docker-compose (docker-compose.yml). Главное — не забыть заменить http://vault:8200 на свой IP...

version: '3.8'
services:
  vault:
    image: hashicorp/vault:latest
    container_name: vault
    restart: unless-stopped
    ports:
      - "8200:8200"
    volumes:
      - ./data:/vault/data:rw
      - ./config/config.hcl:/vault/config/config.hcl:ro
    cap_add:
      - IPC_LOCK
    environment:
      - VAULT_ADDR=http://127.0.0.1:8200
    command: vault server -config=/vault/config/config.hcl

Запускаю Vault. Всё работает — хранилище секретов готово.

docker-compose up -d

Инициализирую Vault. Первый запуск требует ряд манипуляций внутри контейнера.

docker exec -it vault sh
export VAULT_ADDR=http://127.0.0.1:8200

Инициализация (не забудьте сохранить ВСЕ выведенные ключи!):

vault operator init 

Пример вывода:

# Unseal Key 1: YOUR_UNSEAL_KEY
# Initial Root Token: s.YOUR_ROOT_TOKEN

Проверяю статус командой:

vault status

Выхожу из контейнера:

exit

Тестирую сохранение данных:

docker exec -it vault sh
export VAULT_ADDR=http://127.0.0.1:8200
vault login YOUR_ROOT_TOKE

Включаю KV движок:

vault secrets enable -path=secret kv-v2

Создаю тестовый секрет:

vault kv put secret/test message="Hello Persistent Storage" created_by="docker-compose"

Проверяю:

vault kv get secret/test

Выхожу из контейнера:

exit

Перезапускаю контейнер с Vault:

docker-compose restart vault

Проверяю, что секрет на месте:

docker exec -it vault sh
export VAULT_ADDR=http://127.0.0.1:8200

Vault будет запечатан после перезапуска! Сначала нужно его распечатать:

vault operator unseal YOUR_UNSEAL_KEY

Проверяю данные:

vault login YOUR_ROOT_TOKEN
vault kv get secret/test  

Все секреты на месте. Выхожу из контейнера:

exit
Рисунок 3. Стартовая страница Vault
Рисунок 3. Стартовая страница Vault

DefectDojo и Dependency-Track

«Одним махом — двух зайцев», — решил я. На одной виртуальной машине поселю сразу двух помощников: DefectDojo — для сбора всех результатов сканирований, и Dependency-Track — для анализа зависимостей. Они на разных портах сидят, не мешают друг другу. Установку выполним снова через docker-compose, с хранилищем, чтобы при перезапуске ничего не терялось.

DefectDojo оказался заметно сложнее предыдущих сервисов. Помимо самого приложения, для его работы используются PostgreSQL, Redis, Nginx, uWSGI и Celery. Чтобы не потеряться в этом зоопарке контейнеров, кратко разберем роль каждого компонента.

Компонент

Основная роль в DefectDojo

Зачем он нужен

Nginx

Обратный прокси-сервер и сервер для статики

Принимает все входящие HTTP-запросы, обрабатывает HTTPS, отдает статические файлы (CSS, JS, изображения) и, самое главное, перенаправляет запросы к вашему приложению внутреннему серверу uWSGI.

uWSGI

Сервер приложений (Application Server)

Это сервер, на котором непосредственно запущен код DefectDojo (Django). Он обрабатывает динамические запросы от Nginx, обращается к базе данных и возвращает готовые HTML-страницы. Nginx и uWSGI работают в паре.

PostgreSQL

Реляционная база данных

Основное хранилище всех данных: информация о продуктах, тестах, найденных уязвимостях, пользователях, настройках и т.д. PostgreSQL — надежная и производительная СУБД, официально поддерживаемая DefectDojo.

Redis

Брокер сообщений (Message Broker) и кеш

Выступает в роли диспетчера для отложенных задач. Когда приложению нужно выполнить что-то долгое (например, дедупликацию), оно отправляет задачу в Redis, откуда ее заберет Celery. Это позволяет веб-серверу не ждать и обрабатывать новые запросы.

Celery (Worker & Beat)

Фоновый обработчик и планировщик задач

Worker запускает ресурсоемкие задачи в фоне, например, импорт результатов сканирования, дедупликацию уязвимостей или синхронизацию с Jira.

Beat планирует выполнение периодических задач, например, отправку уведомлений о приближающихся сроках тестирования

Чтобы данные не терялись при перезапуске контейнеров, настроим постоянное хранение для базы данных PostgreSQL и медиафайлов DefectDojo. Заодно изменим порт веб-интерфейса на 9090, чтобы избежать конфликта с Dependency-Track, который будет работать на той же виртуальной машине.

Для начала подготовим директории для DefectDojo и выдадим необходимые права:

mkdir ~/defectdojo
cd ~/defectdojo

Создаю все возможные недостающие директории для DefectDojo и раздаю права.

mkdir -p docker/extra_settings
mkdir -p docker/postgres
mkdir -p docker/rabbitmq
mkdir -p docker/data
sudo mkdir -p /opt/defectdojo/media
sudo chown -R 1000:1000 /opt/defectdojo/
sudo chmod -R 755 /opt/defectdojo/
chmod -R 755 docker/

Клонирую репозиторий с необходимыми файлами. В репозитории есть готовые примеры файлов для разных окружений. Копирую нужный файл для работы с docker-compose.

git clone https://github.com/DefectDojo/django-DefectDojo.git
cd django-DefectDojo
cp docker-compose.yml ../.

Дальше — настройка постоянного хранения. Редактирую docker-compose.yml. Прописываю внешнее хранилище, также не забываю сменить порт, чтобы избежать конфликта с Dependency-Track (я поставил 9090). Поскольку на этом этапе стенд будет работать по HTTP, закомментируем строки конфигурации, относящиеся к порту 8443.

Монтирую диски:

services:
  postgres:
    ...
    volumes:
      # Путь на хосте «:» путь в контейнере
      - /opt/defectdojo/postgres:/var/lib/postgresql/data 
    ...

  uwsgi:
    ...
    volumes:
      # Для загруженных файлов
      - /opt/defectdojo/media:/app/media 
                   # Для кастомного nginx так как у нас на одной машине и DT и DD
      - ./nginx/conf.d:/etc/nginx/conf.d:ro 
    ...

Добавляю в секцию uwsgi следующие переменные:

 uwsgi:
    environment:
      DD_FORCE_SSL_REDIRECT: "False"
      DD_SECURE_SSL_REDIRECT: "False"
      DD_CSRF_COOKIE_SECURE: "False"
      DD_SESSION_COOKIE_SECURE: "False"
      DD_SITE_URL: "http://192.168.0.204:9090"

Комментирую строки, отвечающие за порт 8443:

ports:
      - “9090:8080”
      # - target: 8443
      #  published: ${DD_TLS_PORT:-8443}
      # protocol: tcp
      # mode: host

Создаю минимальный кастомный nginx-конфиг:

cd ~/defectdojo
mkdir -p nginx/conf.d

sudo nano  nginx/conf.d/dojo.conf 

Текст конфига:

upstream django {
    server uwsgi:9090;  
}

server {
    listen 8080;
    server_name _;
    client_max_body_size 100M;

    location / {
        uwsgi_pass django;
        include /etc/nginx/uwsgi_params;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /static/ {
        alias /app/static/;
    }

    location /media/ {
        alias /app/media/;
    }
}

Выполняю миграцию базы данных (на всякий случай, так как на форумах по разбору ошибок при установке я часто видел такую проблему):

docker compose exec uwsgi /bin/bash -c "python manage.py migrate"

Создаю суперпользователя:

docker compose exec uwsgi /bin/bash -c "python manage.py createsuperuser"

Получаю доступ к веб-интерфейсу. DefectDojo доступен по адресу http://192.168.0.204:9090/.

Рисунок 4. Главная страница DefectDojo
Рисунок 4. Главная страница DefectDojo

Установка OWASP Dependency-Track

Dependency-Track состоит из двух основных компонентов: API Server и Frontend. Официальный docker-compose.yml уже настроен на использование томов (volumes) для хранения данных базы данных и других файлов. Наша задача — сделать эти тома явными и при необходимости перенаправить их в удобную директорию на хосте.

Создаю директорию для Dependency-Track:

curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash

Редактирую файл для настройки томов (Persistent Volumes):

nano docker-compose.yml

Стандартный файл использует именованные тома Docker (например, dependency-track). Чтобы данные хранились в конкретной папке на моем сервере (например, /opt/dependency-track/data), я заменю их на свое хранилище.

Нахожу секцию volumes для сервиса dependency-track (это API сервер) и меняю ее. Я много помучался с этой историей, так как опыта в разворачивании dependency-track не было. Вот полный конфиг, который запустил сие творение на моей виртуальной машине (для наглядности удалил все стандартные закомментированные строки). Не забудьте поменять IP в API_BASE_URL: http://localhost:8081.

services:
  apiserver:
    image: dependencytrack/apiserver
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      EXTRA_JAVA_OPTIONS: "-Xmx4G -Xms2G -Djava.io.tmpdir=/data/tmp"
      ALPINE_DATABASE_MODE: "external"
      ALPINE_DATABASE_URL: "jdbc:postgresql://postgres:5432/dtrack"
      ALPINE_DATABASE_DRIVER: "org.postgresql.Driver"
      ALPINE_DATABASE_USERNAME: "dtrack"
      ALPINE_DATABASE_PASSWORD: "dtrack"
    deploy:
      resources:
        limits:
          memory: 6g
      restart_policy:
        condition: on-failure
    ports:
    - '8081:8080'
    volumes:
    - "/opt/dependency-track/data:/data"
    - "/opt/dependency-track/tmp:/data/tmp"
    restart: unless-stopped
frontend:
    image: dependencytrack/frontend
    depends_on:
      apiserver:
        condition: service_healthy
    environment:
      API_BASE_URL: "http://localhost:8081"
    ports:
      - "8080:8080"
    restart: unless-stopped

  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: "dtrack"
      POSTGRES_USER: "dtrack"
      POSTGRES_PASSWORD: "dtrack"
      POSTGRES_SHARED_BUFFERS: 256MB
      POSTGRES_EFFECTIVE_CACHE_SIZE: 512MB
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
      interval: 5s
      timeout: 3s
      retries: 3
    volumes:
    - "postgres-data:/var/lib/postgresql/data"
    restart: unless-stopped
volumes:
  dtrack-data: {}
  postgres-data: {}

После настройки запускаю Dependency-Track:

docker compose up -d

Проверяю статус и дожидаюсь инициализации:

docker compose ps
docker compose logs -f

Получаю доступ к веб-интерфейсу:

  • Frontend: http://ip_сервера:8080

  • Логин по умолчанию: admin / admin (система потребует сменить пароль при первом входе).

Рисунок 5. Главная страница Dependency-Track
Рисунок 5. Главная страница Dependency-Track

CLI-инструменты

На пятой виртуальной машине соберем набор утилит для анализа безопасности. Здесь будут жить инструменты для DAST-сканирования, анализа исходного кода и проверки секретов.

Поставим Checkov, Trivy, Gitleaks, OpenGrep, Nuclei и еще несколько инструментов. Часть — через APT, часть — руками из GitHub:

  • Для работы некоторых инструментов потребуется Node.js и npm. Установим их из стандартных репозиториев Ubuntu. 

  • Checkov — через pip. 

  • Trivy — через официальный репозиторий. 

  • Gitleaks — скачиваем бинарник с GitHub. 

  • OpenGrep — через скрипт установки, потом добавляем в PATH и клонируем правила. 

  • Nuclei — скачиваем с GitHub и распаковываем.

Node

Устанавливаю:

sudo apt install nodejs

Проверяю версию:

node --version

NPM

Устанавливаю:

sudo apt install npm

Проверяю версию:

npm --version

Checkov

Устанавливаю:

sudo apt install -y python3-pip
sudo pip install checkov

Проверяю версию:

checkov --version

Trivy

Установка вспомогательных пакетов:

sudo apt-get install -y wget apt-transport-https gnupg lsb-release

Импорт публичного GPG-ключа репозитория Trivy:

wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -

Добавление репозитория Trivy в список источников APT:

echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list

Обновление списка пакетов и установка Trivy:

sudo apt update && sudo apt install -y trivy

Проверяю версию:

trivy --version

Gitleaks

Скачиваю архив с релизом Gitleaks:

wget -O gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.18.1/gitleaks_8.18.1_linux_x64.tar.gz

Распаковываю архив:

tar -xzf gitleaks.tar.gz

Перемещение бинарного файла в системный PATH:

sudo mv gitleaks /usr/local/bin/

Проверяю версию:

gitleaks version

OpenGrep

Установка чуть сложнее. Нужно установить и скачать правила. Для начала мы скачиваем скрипт установки:

curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash

Даем права на выполнение самому бинарному файлу:

chmod +x /home/an/.opengrep/cli/v1.16.3/opengrep

Далее необходимо добавить ~/.local/bin в PATH (постоянно). Сначала проверяю, есть ли там бинарник:

ls -la ~/.local/bin/opengrep

Затем добавляю директорию в PATH, отредактировав ~/.bashrc:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc

Применяю изменения:

source ~/.bashrc

Проверяю версию установленного инструмента:

opengrep --version

Opengrep поддерживает все стандартные правила Semgrep, но у форка есть собственный репозиторий правил, который я обязательно использую. Клонирую официальный репозиторий правил Opengrep.

cd /opt
sudo git clone https://github.com/opengrep/opengrep-rules.git

Очищаю от лишних файлов (git, CI/CD и т. д.):

cd opengrep-rules
sudo rm -rf .git .github .pre-commit-config.yaml

Nuclei

Перед установкой необходимо скачать архив с последней версией Nuclei. Для этого используется инструмент wget:

Wget https://github.com/projectdiscovery/nuclei/releases/download/v3.1.10/nuclei_3.1.10_linux_amd64.zip
unzip nuclei_3.1.10_linux_amd64.zip
sudo mv nuclei /usr/local/bin/

Для Nuclei нужно добавить пути пользовательских бинарников в ~/.bashrc:

export PATH="$HOME/.local/bin:$PATH"

Применяю изменения:

source ~/.bashrc
nuclei --version

Заключение

В результате у нас получилась базовая инфраструктура для стенда безопасной разработки. Мы развернули GitLab, Nexus, HashiCorp Vault, DefectDojo и Dependency-Track, а также подготовили отдельную виртуальную машину с набором CLI-инструментов для анализа безопасности.

Пока это лишь набор сервисов, каждый из которых работает сам по себе. Однако фундамент уже готов: есть система управления исходным кодом, хранилище артефактов, менеджер секретов, инструменты управления уязвимостями и средства автоматизированного анализа.

В следующей части научим жителей нашего царства безопасной разработки жить сообща — то есть перейдем к настройке и интеграции компонентов. Создадим репозитории в Nexus, настроим группы и раннеры в GitLab, подготовим аутентификацию в Vault, заведем продукты в DefectDojo и проекты в Dependency-Track. После этого можно будет переходить к построению полноценного DevSecOps-конвейера.


PURP — Telegram-канал, где кибербезопасность раскрывается с обеих сторон баррикад

t.me/purp_sec — инсайды и инсайты из мира этичного хакинга и бизнес-ориентированной защиты от специалистов Бастиона

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