334 lines
20 KiB
Markdown
334 lines
20 KiB
Markdown
# GuardDog Nexus
|
||
|
||
Интеграция [GuardDog](https://github.com/DataDog/guarddog) (сканер уязвимостей пакетов) с [Sonatype Nexus Repository Manager]. Автоматически сканирует пакеты (PyPI, Go, npm), хранящиеся в Nexus, на наличие уязвимостей, вредоносного кода и подозрительных паттернов при каждом обновлении пакета через вебхуки.
|
||
|
||
## Возможности
|
||
|
||
- **Автоматическое сканирование** по вебхукам Nexus при обновлении пакетов (только `UPDATED`)
|
||
- **Поддержка нескольких экосистем** — PyPI, Go, npm (включая scoped-пакеты `@scope/name`); неизвестные экосистемы явно отклоняются
|
||
- **REST API** для просмотра результатов сканирования, уязвимостей, статистики и экспорта в CSV
|
||
- **Веб-интерфейс** с дашбордом, таблицами сканирований и фильтрацией по уязвимостям
|
||
- **LLM-анализ** — автоматический разбор каждой уязвимости через OpenAI-совместимые API (опционально, настраивается); параллельный анализ через `asyncio.gather`
|
||
- **Дедупликация** по URL и SHA256 — один и тот же пакет сканируется один раз
|
||
- **Защита от SSRF** — валидация URL загрузки через `NEXUS_ALLOWED_HOSTS`
|
||
- **Структурированное логирование** в формате JSON с опциональной отправкой в syslog
|
||
- **Docker Compose** для развёртывания приложения, Nexus и настройки в одном стеке
|
||
|
||
## Архитектура
|
||
|
||
```
|
||
Nexus ──(webhook)──> GuardDog Nexus ──(REST API)──> Веб-интерфейс
|
||
│
|
||
├──> GuardDog CLI (сканирование)
|
||
├──> LLM API (анализ уязвимостей)
|
||
├──> SQLite (хранилище результатов)
|
||
└──> REST API (данные для UI + экспорт CSV)
|
||
```
|
||
|
||
## Быстрый старт
|
||
|
||
### Требования
|
||
|
||
- Docker и Docker Compose
|
||
- Python 3.10+ (для локальной разработки)
|
||
|
||
### Развёртывание в Docker
|
||
|
||
```bash
|
||
# Скопируйте файл конфигурации
|
||
cp .env.example .env
|
||
|
||
# Отредактируйте .env при необходимости (LLM и т.д.)
|
||
|
||
# Запустите стек
|
||
make docker-up
|
||
```
|
||
|
||
После запуска доступны:
|
||
|
||
| Сервис | URL | Порт |
|
||
|--------|-----|------|
|
||
| GuardDog Nexus | http://localhost:8080 | 8080 |
|
||
| Sonatype Nexus | http://localhost:8081 | 8081 |
|
||
|
||
### Локальная разработка
|
||
|
||
```bash
|
||
# Установите зависимости
|
||
make install dev
|
||
|
||
# Настройте переменные окружения
|
||
cp .env.example .env
|
||
export $(cat .env | xargs)
|
||
|
||
# Запустите приложение
|
||
python -m guarddog_nexus.main
|
||
```
|
||
|
||
## Переменные окружения
|
||
|
||
| Переменная | По умолчанию | Описание |
|
||
|------------|-------------|----------|
|
||
| `NEXUS_URL` | `http://localhost:8081` | URL Sonatype Nexus |
|
||
| `NEXUS_ALLOWED_HOSTS` | хост из `NEXUS_URL` | Разрешённые хосты для скачивания (через запятую, защита от SSRF) |
|
||
| `DATABASE_PATH` | `data/guarddog.db` | Путь к SQLite-базе данных |
|
||
| `HOST` | `0.0.0.0` | Хост для прослушивания |
|
||
| `PORT` | `8080` | Порт для прослушивания |
|
||
| `LOG_LEVEL` | `INFO` | Уровень логирования |
|
||
| `LOG_SYSLOG_HOST` | _(пусто)_ | Хост syslog для отправки логов |
|
||
| `LOG_SYSLOG_PORT` | `514` | Порт syslog |
|
||
| `WEBHOOK_SECRET` | _(пусто)_ | Секрет для HMAC-подписи вебхуков |
|
||
| `SCAN_TIMEOUT_SECONDS` | `300` | Таймаут сканирования одного пакета |
|
||
| `TEMP_DIR` | `/tmp/guarddog-nexus` | Временная директория для загрузки пакетов |
|
||
| `GUARDDOG_BINARY` | `guarddog` | Путь к бинарнику GuardDog |
|
||
| `NEXUS_DOWNLOAD_TIMEOUT_SECONDS` | `120` | Таймаут загрузки пакета из Nexus |
|
||
| `NEXUS_API_TIMEOUT_SECONDS` | `30` | Таймаут запросов к Nexus REST API |
|
||
| `MAX_CONCURRENT_SCANS` | `4` | Максимум одновременных сканирований GuardDog |
|
||
| `LOG_SYSLOG_FACILITY` | `local0` | Syslog facility (local0–local7) |
|
||
| `LLM_ENABLED` | `0` | `1` — включить LLM-анализ уязвимостей |
|
||
| `LLM_AUTO_ANALYZE` | `0` | `1` — автоанализ после скана; `0` = ручной режим через кнопку в UI |
|
||
| `LLM_API_KEY` | _(пусто)_ | API-ключ (OpenAI / Groq / Ollama / etc.) |
|
||
| `LLM_API_BASE` | `https://api.openai.com/v1` | Базовый URL OpenAI-совместимого API |
|
||
| `LLM_MODEL` | `gpt-4o-mini` | Название модели |
|
||
| `LLM_TIMEOUT_SECONDS` | `30` | Таймаут запроса к LLM |
|
||
| `LLM_MAX_CONCURRENT_ANALYSES` | `2` | Максимум одновременных LLM-анализов |
|
||
|
||
## Настройка Nexus
|
||
|
||
### Создание репозитория
|
||
|
||
1. Убедитесь, что в Nexus создан репозиторий `pypi-proxy` (прокси на `https://pypi.org`)
|
||
2. Настройте вебхук Nexus для отправки событий на `http://<guarddog-nexus>:8080/webhooks/nexus`
|
||
3. Используйте `scripts/setup-nexus.sh` для автоматической настройки (требует `curl`)
|
||
|
||
### Вебхуки
|
||
|
||
GuardDog Nexus принимает вебхуки от Nexus при событии `UPDATED` (срабатывает при обновлении кэша прокси-репозитория).
|
||
|
||
Для валидации вебхуков установите `WEBHOOK_SECRET` в `.env` — подпись проверяется через HMAC-SHA256.
|
||
|
||
> **Примечание:** Вебхуки доступны в Nexus Pro. В Nexus Repository Manager 3 Community Edition можно использовать плагин [nexus-blobstore-webhook](https://github.com/sonatype-nexus-community/nexus-blobstore-webhook) или настроить вебхуки вручную через административную панель.
|
||
|
||
### Настройка вебхуков в Nexus
|
||
|
||
1. В административной панели Nexus перейдите в **System → Capabilities**
|
||
2. Создайте capability типа **Webhook: Repository**
|
||
3. Укажите **URL:** `http://guarddog-nexus:8080/webhooks/nexus` (замените хост если нужно)
|
||
4. Выберите тип события: **Repository → Asset → Updated**
|
||
5. В фильтре репозиториев выберите: `pypi-proxy`, `go-proxy`, `npm-proxy`
|
||
6. Если задан `WEBHOOK_SECRET`, укажите тот же секрет в поле **Secret Key**
|
||
|
||
> Для локальной разработки без реальных вебхуков можно отправлять тестовые запросы вручную:
|
||
> ```bash
|
||
> curl -X POST http://localhost:8080/webhooks/nexus \
|
||
> -H "Content-Type: application/json" \
|
||
> -d '{"action":"UPDATED","repositoryName":"pypi-proxy","asset":{"format":"pypi","name":"/packages/pkg/1.0/pkg-1.0.tar.gz","downloadUrl":"http://nexus:8081/repository/pypi-proxy/packages/pkg/1.0/pkg-1.0.tar.gz"}}'
|
||
> ```
|
||
|
||
### E2E-тестирование
|
||
|
||
В `examples/` лежат примеры вредоносных пакетов для PyPI, npm и Go с паттернами, которые GuardDog гарантированно обнаружит (`exec-base64`, `shady-links`, `code-execution`, `npm-api-obfuscation`, `go-exec-base64`).
|
||
|
||
Запуск E2E-теста (требует запущенного Docker-стека):
|
||
|
||
```bash
|
||
./examples/trigger-scans.sh
|
||
```
|
||
|
||
Скрипт собирает архивы `.tar.gz` / `.tgz` / `.zip`, копирует их в контейнер и отправляет вебхуки. Через ~15 секунд проверяет результаты: все три пакета должны быть flagged с findings.
|
||
|
||
Для тестирования отдельной экосистемы — curl вручную:
|
||
|
||
```bash
|
||
# PyPI: exec-base64 + shady-links + code-execution
|
||
curl -X POST http://localhost:8080/webhooks/nexus \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"action":"UPDATED","repositoryName":"pypi-proxy","asset":{"format":"pypi","name":"/packages/evil-pypi/0.1.0/evil-pypi-0.1.0.tar.gz","downloadUrl":"http://nexus:8081/repository/pypi-proxy/packages/evil-pypi/0.1.0/evil-pypi-0.1.0.tar.gz"}}'
|
||
|
||
# npm: eval + Buffer(base64) + shady-links
|
||
curl -X POST http://localhost:8080/webhooks/nexus \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"action":"UPDATED","repositoryName":"npm-proxy","asset":{"format":"npm","name":"/packages/evil-npm/-/evil-npm-1.0.0.tgz","downloadUrl":"http://nexus:8081/repository/npm-proxy/evil-npm/-/evil-npm-1.0.0.tgz"}}'
|
||
|
||
# Go: exec + base64 + suspicious HTTP call
|
||
curl -X POST http://localhost:8080/webhooks/nexus \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"action":"UPDATED","repositoryName":"go-proxy","asset":{"format":"go","name":"/packages/github.com/evil/evil-go/@v/v0.1.0.zip","downloadUrl":"http://nexus:8081/repository/go-proxy/github.com/evil/evil-go/@v/v0.1.0.zip"}}'
|
||
```
|
||
|
||
## REST API
|
||
|
||
### Сканирования
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | `/api/v1/scans` | Список сканирований (пагинация, фильтр `flagged`) |
|
||
| GET | `/api/v1/scans/stats` | Статистика: общее количество, уязвимые пакеты, топ правил |
|
||
| GET | `/api/v1/scans/{id}` | Детали конкретного сканирования с результатами |
|
||
| GET | `/api/v1/scans/export` | Экспорт сканирований в CSV |
|
||
|
||
### Пакеты
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | `/api/v1/packages` | Список уникальных пакетов (пагинация, фильтр по экосистеме) |
|
||
| GET | `/api/v1/packages/{name}/{version}` | Все сканирования и уязвимости для пакета |
|
||
| GET | `/api/v1/packages/export` | Экспорт пакетов в CSV |
|
||
|
||
### Уязвимости
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | `/api/v1/findings` | Список уязвимостей (фильтр по правилу, severity, scan_id) |
|
||
| POST | `/api/v1/findings/{id}/analyze` | Запустить LLM-анализ уязвимости (возвращает HTMX-фрагмент при вызове из веб-интерфейса) |
|
||
|
||
### Здоровье и метрики
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | `/health` | Проверка работоспособности |
|
||
| GET | `/health/dependencies` | Проверка БД и доступности Nexus API |
|
||
| GET | `/metrics` | Prometheus-метрики: `guarddog_scans_total`, `guarddog_scans_flagged_total`, `guarddog_findings_total`, `guarddog_llm_analyzed_total`, `guarddog_llm_pending_total`, `guarddog_scans_by_status`, `guarddog_scans_by_ecosystem`, `guarddog_last_scan_timestamp_seconds` |
|
||
|
||
## Веб-интерфейс
|
||
|
||
| Страница | URL | Описание |
|
||
|----------|-----|----------|
|
||
| Дашборд | `/` | Статистика, графики, топ уязвимых пакетов |
|
||
| Сканирования | `/scans` | Таблица всех сканирований с фильтрацией |
|
||
| Детали сканирования | `/scans/{id}` | Результаты одного сканирования |
|
||
| Пакеты | `/packages` | Таблица уникальных пакетов |
|
||
| Детали пакета | `/packages/{name}/{version}` | История сканирований и уязвимости пакета |
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
guarddog-nexus/
|
||
├── guarddog_nexus/ # Основной пакет
|
||
│ ├── main.py # Точка входа FastAPI
|
||
│ ├── config.py # Конфигурация из переменных окружения
|
||
│ ├── constants.py # Централизованные константы
|
||
│ ├── logging_setup.py # JSON-логирование + syslog
|
||
│ ├── core/ # Ядро: сканер, harvester, LLM
|
||
│ │ ├── scanner.py # Интеграция с GuardDog CLI
|
||
│ │ ├── harvester.py # Пайплайн: загрузка → скан → сохранение
|
||
│ │ ├── nexus.py # HTTP-клиент + extractor'ы pypi/go/npm
|
||
│ │ └── llm.py # LLM-клиент (OpenAI-совместимый)
|
||
│ ├── db/ # База данных
|
||
│ │ ├── engine.py # Async SQLAlchemy + миграции
|
||
│ │ ├── models.py # ORM-модели (Scan, Finding)
|
||
│ │ └── queries.py # Общие SQL-запросы
|
||
│ ├── routes/ # Роутеры FastAPI
|
||
│ │ ├── webhooks.py # Приём вебхуков Nexus
|
||
│ │ ├── api_scans.py # REST API: сканирования
|
||
│ │ ├── api_packages.py # REST API: пакеты
|
||
│ │ ├── api_findings.py # REST API: уязвимости
|
||
│ │ └── web.py # Веб-интерфейс (Jinja2 + htmx)
|
||
│ └── web/ # Статика и шаблоны
|
||
│ ├── templates/ # Jinja2-шаблоны
|
||
│ └── static/ # CSS, JS
|
||
├── tests/ # Тесты pytest
|
||
├── scripts/ # Вспомогательные скрипты
|
||
├── docker-compose.yml # Стек Docker Compose
|
||
├── Dockerfile # Образ приложения
|
||
└── pyproject.toml # Зависимости и настройки
|
||
```
|
||
|
||
## Команды Makefile
|
||
|
||
| Команда | Описание |
|
||
|---------|----------|
|
||
| `make install` | Установка зависимостей проекта |
|
||
| `make dev` | Установка зависимостей для разработки |
|
||
| `make test` | Запуск тестов (168) |
|
||
| `make lint` | Проверка кода через Ruff |
|
||
| `make format` | Форматирование кода через Ruff |
|
||
| `make typecheck` | Проверка типов через mypy (strict mode) |
|
||
| `make check` | lint + format + typecheck + test (все проверки) |
|
||
| `make run` | Запуск приложения локально |
|
||
| `make setup-env` | Копирование `.env.example` → `.env` (если отсутствует) |
|
||
| `make docker-build` | Сборка Docker-образа |
|
||
| `make docker-up` | Пересборка и запуск стека (`up -d --build`) |
|
||
| `make docker-down` | Остановка стека |
|
||
| `make docker-destroy` | Остановка стека с удалением всех данных (`-v`) |
|
||
| `make docker-logs` | Просмотр логов стека |
|
||
| `make docker-rebuild` | Полная пересборка (down + build + up) |
|
||
| `make docker-ps` | Статус контейнеров (`docker compose ps`) |
|
||
| `make docker-shell` | Интерактивная оболочка в контейнере |
|
||
| `make docker-restart` | Перезапуск контейнера guarddog-nexus |
|
||
| `make clean` | Очистка артефактов сборки |
|
||
|
||
## Безопасность
|
||
|
||
- Вебхуки поддерживают HMAC-SHA256 подпись через `WEBHOOK_SECRET`
|
||
- Nexus-клиент использует анонимный доступ (без BasicAuth) — убедитесь, что Nexus разрешает анонимное чтение репозиториев
|
||
- Защита от SSRF: URL загрузки валидируется через `NEXUS_ALLOWED_HOSTS`
|
||
- Заголовки безопасности на всех ответах: `X-Content-Type-Options`, `X-Frame-Options`, `X-XSS-Protection`, `Referrer-Policy`, `Permissions-Policy`
|
||
- Результаты сканирования хранятся в локальной SQLite-базе
|
||
- Временные файлы пакетов удаляются после сканирования
|
||
|
||
## LLM-анализ
|
||
|
||
GuardDog Nexus может анализировать найденные уязвимости через LLM (языковую модель). При включении (`LLM_ENABLED=1`) уязвимые находки получают AI-разбор: насколько угроза реальна, что делает подозрительный код, рекомендации.
|
||
|
||
### Режимы работы
|
||
|
||
Переменная `LLM_AUTO_ANALYZE` управляет режимом анализа:
|
||
|
||
- **`LLM_AUTO_ANALYZE=1` (автоматический):** после завершения скана каждая находка автоматически отправляется в LLM. Отчёт сохраняется в БД и включается в syslog-событие. Кнопка анализа в UI не отображается.
|
||
- **`LLM_AUTO_ANALYZE=0` (ручной, по умолчанию):** в веб-интерфейсе рядом с каждой уязвимостью отображается кнопка «Analyze with LLM». Пользователь нажимает кнопку — запускается анализ, результат показывается inline.
|
||
|
||
### Состояния finding.report
|
||
|
||
Поле `finding.report` проходит через конечный автомат:
|
||
|
||
| Значение | UI |
|
||
|----------|----|
|
||
| `None` | Кнопка «Analyze with LLM» (только в ручном режиме) |
|
||
| `{"status": "analyzing"}` | Спиннер |
|
||
| `{verdict:, summary:, ...}` | Отчёт + ссылка «Retry» |
|
||
|
||
### Поддерживаемые провайдеры
|
||
|
||
Любой OpenAI-совместимый API. Примеры конфигурации:
|
||
|
||
```bash
|
||
# OpenAI (ручной режим)
|
||
LLM_ENABLED=1
|
||
LLM_AUTO_ANALYZE=0
|
||
LLM_API_KEY=sk-...
|
||
LLM_API_BASE=https://api.openai.com/v1
|
||
LLM_MODEL=gpt-4o-mini
|
||
|
||
# Groq с автоанализом (быстрее, бесплатный тир)
|
||
LLM_ENABLED=1
|
||
LLM_AUTO_ANALYZE=1
|
||
LLM_API_KEY=gsk_...
|
||
LLM_API_BASE=https://api.groq.com/openai/v1
|
||
LLM_MODEL=llama-3.3-70b-versatile
|
||
|
||
# Локальный Ollama
|
||
LLM_ENABLED=1
|
||
LLM_API_KEY=ollama
|
||
LLM_API_BASE=http://host.docker.internal:11434/v1
|
||
LLM_MODEL=llama3.2
|
||
```
|
||
|
||
### Формат ответа
|
||
|
||
LLM возвращает JSON с полями:
|
||
- `verdict` — `safe` / `suspicious` / `malicious`
|
||
- `summary` — вердикт в одно предложение
|
||
- `analysis` — подробный разбор (2–3 абзаца)
|
||
- `severity_rating` — `low` / `medium` / `high` / `critical`
|
||
|
||
Без LLM (`LLM_ENABLED=0`) весь остальной функционал работает как обычно.
|
||
|
||
## Лицензия
|
||
|
||
MIT
|
||
|
||
---
|
||
|
||
> **⚠ Весь код в этом репозитории сгенерирован AI-ассистентом (Claude).** Тщательно проверяйте код перед использованием в production-окружении.
|