Skip to content

Self-host эксплуатация

Команды, развёртывающие CVE Radar на внутренних серверах, Kubernetes или Docker, нуждаются в возможностях production из той же MIT-кодовой базы: audit-логи JSON, mount секретов, RBAC, мультитenant PostgreSQL, метрики Prometheus, offline-зеркала и обнаружение стека в Kubernetes.

Enterprise-уровня и лицензионных блокировок нет. Переменные окружения меняют только операционное поведение. См. Эксплуатация и Конфигурация.

flowchart TB
  classDef ui fill:#e9edf5,stroke:#00baba,color:#253343
  classDef api fill:#f3fcfc,stroke:#008c8c,color:#253343
  classDef data fill:#fff7ed,stroke:#eda232,color:#253343
  classDef ext fill:#f5f5f5,stroke:#666,color:#253343

  subgraph deploy["Self-host развёртывание"]
    Browser[Browser SPA]:::ui
    API[Express API]:::api
    PG[(PostgreSQL опционально)]:::data
    Metrics["GET /metrics"]:::api
    Audit[stdout audit JSON]:::ext
  end

  Browser --> API
  API --> PG
  API --> Metrics
  API --> Audit
  API --> Feeds[NVD / OSV / mirrors]:::ext
  API --> Notify[Slack / SMTP / webhooks]:::ext

На схеме: опциональный Postgres, scrape Prometheus на /metrics, audit в stdout и исходящие уведомления после watch. В airgap публичные feeds заменяются локальными зеркалами (см. Airgap).

Audit-логирование

Одна JSON-строка на событие в stdout для ELK/Loki/Splunk или Docker json-file.

action Когда
scan После каждого POST /api/scan
watch После каждого POST /api/watch
health При AUDIT_HEALTH=true и detailed health
export Пока только в браузере
{
  "audit": true,
  "ts": "2026-06-06T14:30:00.000Z",
  "action": "scan",
  "ip": "10.0.0.42",
  "stack": ["redis", "nginx"],
  "duration_ms": 8420,
  "sources_failed": ["NVD"],
  "result_count": 12,
  "mode": "full"
}

TRUST_PROXY_HOPS за reverse proxy. Заголовки CVE и ключи API не логируются.

docker logs cve-radar 2>&1 | grep '"audit":true'

Секреты

env *_FILE
NVD_API_KEY NVD_API_KEY_FILE
GITHUB_TOKEN GITHUB_TOKEN_FILE
DEEPL_API_KEY DEEPL_API_KEY_FILE
ALERT_WEBHOOK_URL ALERT_WEBHOOK_URL_FILE
NOTIFICATION_SLACK_WEBHOOK_URL
NOTIFICATION_DISCORD_WEBHOOK_URL
NOTIFICATION_TELEGRAM_BOT_TOKEN
NOTIFICATION_SMTP_PASS mount через env file в Compose
API_SECRET API_SECRET_FILE
API_SECRET_PREVIOUS API_SECRET_PREVIOUS_FILE

Пример Compose: docker-compose.secrets.example.yml. В Kubernetes — mount Secret как файлы.

RBAC и API

С API_SECRET все /api/* кроме GET /api/health и GET /api/v1/health требуют ключ. API_ROLE:

Роль Права
admin Настройки, tenant CRUD, scan/watch, translate, meta и history
scanner scan/watch/validate, translate, meta и history
viewer meta и history — без scan
auditor history/trends и meta — без scan

По умолчанию admin. viewer на POST /api/scan получает 403 { "code": "FORBIDDEN" }.

Пакет аудита безопасности

Для enterprise / GRC (#259):

Документ Назначение
docs/security/THREAT_MODEL.md STRIDE-lite угрозы
docs/security/DEPENDENCY_AUDIT.md Процедура аудита зависимостей
docs/self-hosted/SECRETS.md Ротация API_SECRET + API_SECRET_PREVIOUS без простоя
SECURITY.md OIDC и SAML через federation → OIDC (без native SP; ADR 007)

Уведомления watch

Self-host часто нужны out-of-band алерты при новых CVE в watch. CVE Radar отправляет уведомления асинхронно после POST /api/watch с непустым newVulns — ответ HTTP не меняется.

Канал env
Slack NOTIFICATION_SLACK_WEBHOOK_URL или legacy ALERT_WEBHOOK_URL
Discord NOTIFICATION_DISCORD_WEBHOOK_URL
Telegram NOTIFICATION_TELEGRAM_BOT_TOKEN, NOTIFICATION_TELEGRAM_CHAT_ID
SMTP NOTIFICATION_SMTP_HOST, FROM, TO; опционально PORT, USER, PASS
Webhook NOTIFICATION_WEBHOOK_URL
Параметр env По умолчанию
Мин. severity NOTIFICATION_MIN_SEVERITY / ALERT_MIN_SEVERITY HIGH
Dedup (ms) NOTIFICATION_DEDUP_MS 900000
Формат Slack ALERT_WEBHOOK_FORMAT slack / generic

Legacy ALERT_WEBHOOK_URL работает через NotificationService. NOTIFICATION_* поддерживает mounts *_FILE. Проверка: GET /api/health?detailed=truealerts.webhookConfigured. См. Оповещения и NOTIFICATIONS.md.

Мультитenant (PostgreSQL)

DATABASE_URL=postgres://cve_radar:cve_radar@127.0.0.1:5432/cve_radar

Migrations при первом подключении pool. Schema в server/db/schema.ts (Drizzle ORM) для постепенной ORM-миграции; runtime-запросы tenant/stack и scan-history используют Drizzle через getDb() (общий pool pg).

X-Tenant-Id: arvancloud-sre
Method Path Описание
POST /api/v1/tenants Создать tenant
GET /api/v1/tenants/stacks Список стеков
POST /api/v1/tenants/stacks Создать стек
GET /api/v1/tenants/stacks/:id Стек по UUID
PUT /api/v1/tenants/stacks/:id Обновить стек
DELETE /api/v1/tenants/stacks/:id Удалить стек
GET /api/v1/scans/history History tenant
GET /api/v1/scans/trends Trends tenant

History фильтруется по tenant_id. Legacy /api/* пишет в default.

При неверном X-Tenant-Id API возвращает 400 TENANT_SLUG_INVALID — без тихого fallback на default.

Чек-лист мультитenant

Тема Поведение
Изоляция данных SQL с WHERE tenant_id = … для стеков, history и prefs уведомлений.
Проверка header Валидный slug при отправке; неизвестный → 404 TENANT_NOT_FOUND.
Зарезервированные slug default, system, admin, root, postgres, public нельзя при создании tenant (409 TENANT_SLUG_RESERVED).
Auth / RBAC API_SECRET и опциональный OIDC; admin-маршруты tenant требуют admin.
Rate limit scan/watch на IP + tenant в v1.
Audit Строки audit с tenant; GET /api/v1/audit только для текущего tenant.
Уведомления Prefs и webhooks tenant при X-Tenant-Id; dedup включает slug.
Legacy /api/* без версии пишет history в default.
Health GET /api/health?detailed=truedatabase.multiTenant; capabilities → features.multiTenant.

Docker Compose: docker compose --profile postgres up -d и DATABASE_URL — см. TENANTS.md.

Prometheus и Grafana

GET /metrics. METRICS_ENABLED (true), METRICS_PROTECT для auth.

Метрика Тип Описание
cve_radar_scans_total counter scan/watch
cve_radar_vulns_found gauge последний успешный scan
cve_radar_scan_duration_seconds histogram длительность
cve_radar_source_reachable gauge upstream
cve_radar_mirror_age_days gauge mirror
cve_radar_cache_entries_total gauge размер cache

Дашборд: docs/self-hosted/grafana/cve-radar-dashboard.json.

sum(rate(cve_radar_scans_total{status="success"}[5m]))
  / sum(rate(cve_radar_scans_total[5m]))

Airgap-развёртывание

AIRGAPPED=true
NVD_MIRROR_URL=http://internal-mirror/nvd
KEV_MIRROR_URL=http://internal-mirror/kev/catalog.json
# OSV — mirror или bulk:
OSV_MIRROR_URL=http://internal-mirror/osv
# OSV_BULK_PATH=/data/osv/extracted
# MIRROR_MANIFEST_PATH=/data/mirrors/.mirror-manifest.json
# MIRROR_STALE_DAYS=7

С OSV_BULK_PATH OSV читается из локального JSON (npm run mirrors:sync / make sync-osv-bulk) без query API. GitHub/RSS/перевод отключены. Нет mirror env для NVD/KEV → fail-closed.

curl -s 'http://localhost:3001/api/health?detailed=true' | jq '.airgap.mirrorHealth'

Боковая панель показывает предупреждение об устаревшем зеркале, если возраст превышает MIRROR_STALE_DAYS (по умолчанию 7). Prometheus: cve_radar_mirror_age_days.

Синхронизация зеркал (зона с интернетом)

npm run mirrors:sync (или make mirrors-sync) загружает KEV, страницы NVD 2.0 (MIRROR_NVD_DAYS) и OSV bulk с manifest и SHA256. Укажите MIRROR_MANIFEST_PATH на air-gap хосте. RPO/RTO и размер диска: docs/self-hosted/AIRGAP.md.

Флаг Эффект
MIRROR_SKIP_NVD=true только KEV + OSV
MIRROR_SKIP_OSV=true только NVD + KEV
MIRROR_SKIP_KEV=true только NVD + OSV

CI проверяет JSON-образцы по schemas/airgap/*.json (npm run check:airgap-schemas).

Helm chart

Официальный chart: charts/cve-radar/. Deployment, Service, опциональный Ingress, Redis, опциональный Postgres, PVC для /app/data, Secret mounts через *_FILE.

helm install cve-radar ./charts/cve-radar -n cve-radar --create-namespace

См. docs/self-hosted/HELM.md.

Kubernetes Operator (CVEScanStack)

Operator v0 согласует CR CVEScanStack с CronJob для agent-сканирования и watch. CRD: cve-radar.io/v1alpha1; манифесты в deploy/k8s/operator/. In-cluster: npm run operator:rundocs/self-hosted/OPERATOR.md.

kubectl apply -f deploy/k8s/operator/crd-cvescanstack.yaml
kubectl apply -f deploy/k8s/operator/rbac.yaml
kubectl apply -f deploy/k8s/operator/deployment.yaml

Каждый CVEScanStack задаёт stack, apiKeySecretRef, опциональный tenantId и cron для import и watch. #254.

Scan job worker (BullMQ)

When REDIS_URL is set, run a separate worker alongside the API: npm run worker:run (production image: node dist-server/server/worker/run.js). The API accepts POST /api/v1/scan/queue; clients poll GET /api/v1/jobs/:id and fetch paginated results from GET /api/v1/jobs/:id/results. The Web UI switches to queue + poll when /api/capabilities reports features.jobQueue: true. Without Redis, scans stay synchronous (POST /api/v1/scan/stream). See #257.

REDIS_URL=redis://127.0.0.1:6379 npm run worker:run

Scan agent (Trivy / Grype)

POST /api/v1/scans/import для JSON Trivy/Grype. CLI: npm run agent:scan. Пример CronJob: deploy/k8s/agent-scan-cronjob.example.yaml. docs/self-hosted/AGENT.md.

Kubernetes discovery

K8S_DISCOVERY_ENABLED=true
# K8S_DISCOVERY_NAMESPACES=production,staging

GET /api/v1/discovery/kubernetes — auth в production. Отключено → 503 K8S_DISCOVERY_DISABLED.

{
  "enabled": true,
  "images": ["haproxy", "nginx", "redis"],
  "tools": ["HAProxy", "Nginx", "Redis"],
  "unmapped": ["my-sidecar"]
}

SA только read на deployments. Сочетайте с tenant.

Краткий справочник

Тема env
Audit AUDIT_HEALTH, TRUST_PROXY_HOPS
Secrets *_FILE
RBAC API_SECRET, API_ROLE
Уведомления NOTIFICATION_*, ALERT_WEBHOOK_URL
Tenant DATABASE_URL, X-Tenant-Id
Metrics METRICS_ENABLED, METRICS_PROTECT
Airgap AIRGAPPED, *_MIRROR_URL, OSV_BULK_PATH, MIRROR_MANIFEST_PATH, MIRROR_STALE_DAYS
K8s K8S_DISCOVERY_ENABLED, K8S_DISCOVERY_NAMESPACES
Enrichment EPSS_ENABLED, COMPLIANCE_ENABLED

Maintainer-копии: docs/self-hosted/.

Домой · Назад: Эксплуатация