From 6e6f45ce0353a2aa7a8d7e081642ff52101a6b7b Mon Sep 17 00:00:00 2001 From: Marker689 Date: Sun, 10 May 2026 07:23:43 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=84=D0=B0=D0=B7=D0=B0=202=20=E2=80=94?= =?UTF-8?q?=20=D0=BA=D1=80=D0=B8=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=84=D0=B8=D0=BA=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit READМЕ: убрать NEXUS_REPOSITORIES, CREATED→UPDATED, go/npm/Gem→go/npm, добавить MAX_CONCURRENT_SCANS, CSV-экспорт, инструкцию по вебхукам Nexus Dockerfile: uv pip install --system . (единый источник deps — pyproject.toml) docker-compose: WEBHOOK_SECRET, SCAN_TIMEOUT_SECONDS pyproject.toml: убрать deprecated [tool.ruff].select config.py: default из DEFAULT_MAX_CONCURRENT_SCANS constants.py: убрать GUARDDOG_ERRORS_KEY (мёртвый), .gem из PACKAGE_EXTENSIONS, LLM prompt: «Python»→«software» queries.py: убрать return_total Makefile: docker-up +--build, docker-down без -v, +docker-destroy, +docker-rebuild, убран typecheck --- Dockerfile | 3 +- Makefile | 13 ++++--- README.md | 72 ++++++++++++++++++++++-------------- docker-compose.yml | 2 + guarddog_nexus/config.py | 5 ++- guarddog_nexus/constants.py | 5 +-- guarddog_nexus/db/queries.py | 1 - pyproject.toml | 3 +- 8 files changed, 62 insertions(+), 42 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7360b18..30e2272 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,7 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ WORKDIR /app COPY pyproject.toml ./ -RUN uv pip install --system fastapi uvicorn[standard] jinja2 httpx \ - "sqlalchemy[asyncio]" aiosqlite python-multipart +RUN uv pip install --system . RUN uv pip install --system guarddog diff --git a/Makefile b/Makefile index d347933..d5b91fb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install dev test lint format typecheck docker-build docker-up docker-down clean +.PHONY: install dev test lint format docker-build docker-up docker-down docker-destroy docker-rebuild docker-logs clean install: pip install -e . @@ -16,18 +16,21 @@ format: ruff format guarddog_nexus tests ruff check --fix guarddog_nexus tests -typecheck: - mypy guarddog_nexus - docker-build: docker compose build docker-up: - docker compose up -d + docker compose up -d --build docker-down: + docker compose down + +docker-destroy: docker compose down -v +docker-rebuild: + docker compose down && docker compose up -d --build + docker-logs: docker compose logs -f diff --git a/README.md b/README.md index 6702c56..70da52a 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ ## Возможности - **Автоматическое сканирование** по вебхукам Nexus при создании/обновлении пакетов -- **Поддержка нескольких экосистем** — PyPI, Gem, и другие форматы через Nexus -- **REST API** для просмотра результатов сканирования, уязвимостей и статистики +- **Поддержка нескольких экосистем** — PyPI, Go, npm (любой формат через прокси-репозитории Nexus) +- **REST API** для просмотра результатов сканирования, уязвимостей, статистики и экспорта в CSV - **Веб-интерфейс** с дашбордом, таблицами сканирований и фильтрацией по уязвимостям - **LLM-анализ** — автоматический разбор каждой уязвимости через OpenAI-совместимые API (опционально, настраивается) - **Дедупликация** по URL и SHA256 — один и тот же пакет сканируется один раз @@ -72,7 +72,6 @@ python -m guarddog_nexus.main | `NEXUS_URL` | `http://localhost:8081` | URL Sonatype Nexus | | `NEXUS_USERNAME` | `admin` | Имя пользователя Nexus | | `NEXUS_PASSWORD` | _(обязательно)_ | Пароль пользователя Nexus | -| `NEXUS_REPOSITORIES` | _(пусто)_ | Список репозиториев через запятую | | `DATABASE_PATH` | `data/guarddog.db` | Путь к SQLite-базе данных | | `HOST` | `0.0.0.0` | Хост для прослушивания | | `PORT` | `8080` | Порт для прослушивания | @@ -85,6 +84,7 @@ python -m guarddog_nexus.main | `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_API_KEY` | _(пусто)_ | API-ключ (OpenAI / Groq / Ollama / etc.) | @@ -102,14 +102,27 @@ python -m guarddog_nexus.main ### Вебхуки -Nexus отправляет вебхуки при событиях `ASSET` и `COMPONENT`. GuardDog Nexus поддерживает: +GuardDog Nexus принимает вебхуки от Nexus при событии `UPDATED` (срабатывает при обновлении кэша прокси-репозитория). -- **CREATED** — новое событие при создании пакета -- **UPDATED** — событие при обновлении пакета +Для валидации вебхуков установите `WEBHOOK_SECRET` в `.env` — подпись проверяется через HMAC-SHA256. -Для валидации вебхуков установите `WEBHOOK_SECRET` — подпись проверяется через HMAC-SHA256. +> **Примечание:** Вебхуки доступны в Nexus Pro. В Nexus Repository Manager 3 Community Edition можно использовать плагин [nexus-blobstore-webhook](https://github.com/sonatype-nexus-community/nexus-blobstore-webhook) или настроить вебхуки вручную через административную панель. -> **Примечание:** Вебхуки доступны в Nexus Pro. В Nexus Repository Manager 3 Community Edition настройка вебхуков может потребовать дополнительных плагинов. +### Настройка вебхуков в 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"}}' +> ``` ## REST API @@ -160,24 +173,26 @@ guarddog-nexus/ ├── guarddog_nexus/ # Основной пакет │ ├── main.py # Точка входа FastAPI │ ├── config.py # Конфигурация из переменных окружения -│ ├── database.py # Async SQLAlchemy + aiosqlite -│ ├── models.py # ORM-модели (Scan, Finding) -│ ├── logging_setup.py # JSON-логирование + syslog -│ ├── harvester.py # Пайплайн: загрузка → сканирование → сохранение -│ ├── scanner.py # Интеграция с GuardDog CLI -│ ├── nexus_client.py # HTTP-клиент для Nexus REST API -│ ├── webhooks.py # Приём вебхуков Nexus -│ ├── api/ # REST API (JSON) -│ │ ├── scans.py -│ │ ├── packages.py -│ │ └── findings.py -│ ├── web/ # Веб-интерфейс -│ │ ├── routes.py -│ │ ├── templates/ # Jinja2-шаблоны -│ │ └── static/ # CSS, JS │ ├── constants.py # Централизованные константы -│ ├── queries.py # Общие SQL-запросы -│ └── llm.py # LLM-клиент +│ ├── 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 @@ -194,11 +209,12 @@ guarddog-nexus/ | `make test` | Запуск тестов | | `make lint` | Проверка кода через Ruff | | `make format` | Форматирование кода через Ruff | -| `make typecheck` | Проверка типов через mypy | | `make docker-build` | Сборка Docker-образа | -| `make docker-up` | Запуск стека Docker Compose | -| `make docker-down` | Остановка стека с удалением данных | +| `make docker-up` | Пересборка и запуск стека (`up -d --build`) | +| `make docker-down` | Остановка стека | +| `make docker-destroy` | Остановка стека с удалением всех данных (`-v`) | | `make docker-logs` | Просмотр логов стека | +| `make docker-rebuild` | Полная пересборка (down + build + up) | | `make clean` | Очистка артефактов сборки | ## Безопасность diff --git a/docker-compose.yml b/docker-compose.yml index 213a10b..264ca1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,8 @@ services: LLM_MODEL: "${LLM_MODEL:-gpt-4o-mini}" LLM_TIMEOUT_SECONDS: "${LLM_TIMEOUT_SECONDS:-30}" MAX_CONCURRENT_SCANS: "${MAX_CONCURRENT_SCANS:-4}" + WEBHOOK_SECRET: "${WEBHOOK_SECRET:-}" + SCAN_TIMEOUT_SECONDS: "${SCAN_TIMEOUT_SECONDS:-300}" volumes: - ./data:/data depends_on: diff --git a/guarddog_nexus/config.py b/guarddog_nexus/config.py index cbf1f65..38672ca 100644 --- a/guarddog_nexus/config.py +++ b/guarddog_nexus/config.py @@ -4,6 +4,7 @@ import os from dataclasses import dataclass from guarddog_nexus.constants import ( + DEFAULT_MAX_CONCURRENT_SCANS, GUARDDOG_BINARY_FALLBACK, HTTP_TIMEOUT_API, HTTP_TIMEOUT_DOWNLOAD, @@ -46,7 +47,9 @@ class Config: scan_timeout_seconds: int = int(os.getenv("SCAN_TIMEOUT_SECONDS", "300")) temp_dir: str = os.getenv("TEMP_DIR", "/tmp/guarddog-nexus") guarddog_binary: str = os.getenv("GUARDDOG_BINARY", GUARDDOG_BINARY_FALLBACK) - max_concurrent_scans: int = int(os.getenv("MAX_CONCURRENT_SCANS", "4")) + max_concurrent_scans: int = int( + os.getenv("MAX_CONCURRENT_SCANS", str(DEFAULT_MAX_CONCURRENT_SCANS)) + ) # LLM analysis llm_enabled: bool = os.getenv("LLM_ENABLED", "").lower() in ("1", "true", "yes") diff --git a/guarddog_nexus/constants.py b/guarddog_nexus/constants.py index abdc5fc..9747e9b 100644 --- a/guarddog_nexus/constants.py +++ b/guarddog_nexus/constants.py @@ -11,7 +11,7 @@ used across the codebase live here to avoid duplication and drift. # Unified list of recognised package file extensions. # NOTE: webhooks uses this to decide whether to accept an asset; # harvester uses it to decide whether to download and scan. -PACKAGE_EXTENSIONS = (".tar.gz", ".tgz", ".whl", ".zip", ".gem") +PACKAGE_EXTENSIONS = (".tar.gz", ".tgz", ".whl", ".zip") # Prefix used in PyPI-style asset paths ("/packages/name/ver/file") PYPI_PATH_PREFIX = "packages" @@ -105,7 +105,6 @@ DEFAULT_MAX_CONCURRENT_SCANS = 4 GUARDDOG_OUTPUT_KEY = "--output-format" GUARDDOG_OUTPUT_FORMAT = "json" GUARDDOG_RESULTS_KEY = "results" -GUARDDOG_ERRORS_KEY = "errors" SCAN_ERROR_TIMEOUT = "timeout" SCAN_ERROR_BINARY_NOT_FOUND = "guarddog_not_found" @@ -156,7 +155,7 @@ LLM_DEFAULT_MODEL = "gpt-4o-mini" LLM_DEFAULT_API_BASE = "https://api.openai.com/v1" LLM_DEFAULT_TIMEOUT = 30 LLM_ANALYSIS_SYSTEM_PROMPT = ( - "You are a security analyst reviewing GuardDog findings for a Python package. " + "You are a security analyst reviewing GuardDog findings for a software package. " "Given a finding (rule name, severity, message, code snippet, location), " "provide a concise security analysis in 2-3 paragraphs. " "Assess whether this is likely a real threat or a false positive. " diff --git a/guarddog_nexus/db/queries.py b/guarddog_nexus/db/queries.py index aca80f3..9692aa0 100644 --- a/guarddog_nexus/db/queries.py +++ b/guarddog_nexus/db/queries.py @@ -35,7 +35,6 @@ def build_scan_list_query( sort_dir: str = "desc", limit: int = 50, offset: int = 0, - return_total: bool = True, ): """Builds a filtered, sorted, paginated query for scans. diff --git a/pyproject.toml b/pyproject.toml index 8c01211..a124609 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,8 @@ dev = [ guarddog-nexus = "guarddog_nexus.main:main" [tool.ruff] -target-version = "py310" line-length = 100 -select = ["E", "F", "I", "W"] +target-version = "py310" [tool.ruff.lint] select = ["E", "F", "I", "W"]