Files
guarddog-nexus/README.md

334 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (local0local7) |
| `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` — подробный разбор (23 абзаца)
- `severity_rating``low` / `medium` / `high` / `critical`
Без LLM (`LLM_ENABLED=0`) весь остальной функционал работает как обычно.
## Лицензия
MIT
---
> **⚠ Весь код в этом репозитории сгенерирован AI-ассистентом (Claude).** Тщательно проверяйте код перед использованием в production-окружении.