Files
dipl-edr/docs/technical_reference.md
T
2026-05-29 17:36:21 +04:00

882 lines
35 KiB
Markdown
Raw 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.
# Технический справочник системы EDR
## Endpoint Detection and Response — подробная документация реализации
---
## Содержание
1. [Архитектура системы](#1-архитектура-системы)
2. [Агент мониторинга](#2-агент-мониторинга)
3. [Модуль машинного обучения](#3-модуль-машинного-обучения)
4. [Серверная часть — API](#4-серверная-часть--api)
5. [База данных](#5-база-данных)
6. [Веб-интерфейс](#6-веб-интерфейс)
7. [Развёртывание (Docker)](#7-развёртывание-docker)
8. [Конфигурация и переменные окружения](#8-конфигурация-и-переменные-окружения)
9. [Расхождения с дипломным документом](#9-расхождения-с-дипломным-документом)
---
## 1. Архитектура системы
### Компоненты
```
┌──────────────────────────────────────────────────────────────────────┐
│ Linux-хост (объект мониторинга) │
│ │
│ ┌──────────────────┐ HTTP POST /api/metrics │
│ │ Агент │ ──────────────────────────▶ ┌───────────────┐ │
│ │ agent/main.py │ │ Бэкенд │ │
│ │ │ │ FastAPI │ │
│ │ • psutil │ │ :8000 │ │
│ │ • watchdog │ │ │ │
│ │ • EnsembleDetector │ • SQLite │ │
│ └──────────────────┘ │ • WebSocket │ │
│ └───────┬───────┘ │
│ │ │
│ WS /ws (push) │
│ │ │
│ ┌───────▼───────┐ │
│ │ Фронтенд │ │
│ │ React SPA │ │
│ │ :3000 │ │
│ └───────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
```
### Поток данных (один цикл, 5 секунд)
```
psutil.process_iter() watchdog inotify
│ │
▼ ▼
collect_processes() _FSHandler._push()
│ │
▼ │
EnsembleDetector.predict() │
│ │
└──────────────┬────────────┘
send_batch() → POST /api/metrics
┌─────────────┼───────────────┐
▼ ▼ ▼
INSERT metrics INSERT file_events INSERT system_metrics
аномалии найдены?
│ да
┌───────▼──────────┐
│ дедупликация 30с │
└───────┬──────────┘
новая? │ существующая? │
▼ ▼
INSERT alerts UPDATE alerts
+ WS broadcast count+1
```
---
## 2. Агент мониторинга
### Файлы
| Файл | Назначение |
|------|-----------|
| `agent/main.py` | Цикл сбора метрик, watchdog, отправка батчей |
| `agent/model.py` | `EnsembleDetector` — ML-ансамбль |
| `agent/train.py` | Standalone-скрипт для предварительного обучения |
| `agent/requirements.txt` | Зависимости Python |
| `agent/Dockerfile` | Образ агента |
### Сбор метрик процессов
```python
# Атрибуты, запрашиваемые через psutil (psutil 6.x убрал 'connections' из attrs)
attrs = ["pid", "name", "cpu_percent", "memory_info", "open_files"]
# CPU нормализован на число логических ядер
cpu_percent_normalized = info["cpu_percent"] / _CPU_COUNT
```
**Важно: нормализация CPU.** `psutil` возвращает суммарную процессорную нагрузку — на 4-ядерной машине процесс может показывать 400%. Агент делит на `psutil.cpu_count(logical=True)`, приводя значение к диапазону 0–100% от мощности всей машины. Это обеспечивает корректное сравнение признаков между хостами с разным числом ядер.
### Совместимость `net_connections`
В `psutil` 6.x метод `Process.connections()` переименован в `Process.net_connections()`. Агент обходит это через:
```python
def _proc_connections(proc: psutil.Process) -> int:
for method in ("net_connections", "connections"):
fn = getattr(proc, method, None)
if fn is None:
continue
try:
return len(fn())
except (psutil.NoSuchProcess, psutil.AccessDenied, OSError):
return 0
return 0
```
### Прогрев CPU
Первый вызов `cpu_percent()` в psutil всегда возвращает 0.0 — библиотека требует временного интервала для расчёта дельты. При старте выполняется:
```python
psutil.cpu_percent(interval=None) # системный вызов
for proc in psutil.process_iter(["cpu_percent"]):
proc.cpu_percent(interval=None) # на процесс
time.sleep(1) # интервал измерения
```
### Мониторинг файловой системы
| Директория | Причина наблюдения |
|-----------|-------------------|
| `/etc` | Persistence-атаки через изменение конфигурации |
| `/tmp` | Типичное место загрузки malware-полезной нагрузки |
| `/var/log` | Попытки зачистки следов атаки |
Буфер событий ограничен 500 записями (FIFO). При переполнении самые старые события вытесняются.
### Формат батча (JSON → POST /api/metrics)
```json
{
"timestamp": "2025-05-28T14:30:00.000000+00:00",
"processes": [
{
"pid": 1234,
"name": "nginx",
"cpu_percent": 2.1,
"memory_mb": 45.3,
"open_files": 22,
"connections": 8,
"is_anomaly": false
}
],
"system": {
"cpu_percent": 12.5,
"ram_percent": 34.2,
"network_connections": 47
},
"file_events": [
{
"path": "/tmp/suspicious.sh",
"event_type": "create",
"timestamp": "2025-05-28T14:29:58.000000+00:00"
}
]
}
```
Поле `is_anomaly` заполняется агентом на основе результата `EnsembleDetector.predict()` **до** отправки. Бэкенд дополнительно проверяет пороговые значения.
---
## 3. Модуль машинного обучения
### Класс
`agent/model.py``EnsembleDetector` (экспортируется как `AnomalyDetector` для обратной совместимости)
```python
AnomalyDetector = EnsembleDetector # алиас в конце model.py
```
### Вектор признаков
```python
FEATURE_COLS = ["cpu_percent", "memory_mb", "open_files", "connections"]
```
| Признак | Тип | Индикатор компрометации |
|---------|-----|------------------------|
| `cpu_percent` | float, 0–100 | Криптомайнеры, брутфорс, ransomware |
| `memory_mb` | float, МБ | Heap overflow, утечки памяти |
| `open_files` | int | Шифровальщики (mass file access) |
| `connections` | int | C2-коммуникации, DDoS, сканирование |
### Предобработка: RobustScaler
$$x' = \frac{x - \text{median}(X_{\text{train}})}{\text{IQR}(X_{\text{train}})}$$
Параметры сохраняются в `data/model.pkl` под ключом `"scaler"` и применяются при инференсе без повторного обучения.
### Алгоритмы ансамбля
```python
IsolationForest(
n_estimators=100, # число деревьев изоляции
contamination=0.01, # ожидаемая доля аномалий: 1%
random_state=42,
n_jobs=-1, # параллельное обучение на всех ядрах
)
LocalOutlierFactor(
n_neighbors=20, # размер локального окружения k
contamination=0.01,
novelty=True, # режим инференса на новых данных (обязательно!)
n_jobs=-1,
)
OneClassSVM(
kernel="rbf", # гауссово ядро (RBF)
nu=0.01, # верхняя оценка доли аномалий
gamma="scale", # γ = 1 / (n_features * Var(X))
)
```
### Правило голосования
Каждая модель возвращает $v_i \in \{+1, -1\}$:
- $+1$ — нормальный процесс
- $-1$ — аномальный
$$\text{is\_anomaly} = \left[\sum_{i=1}^{3} v_i \leq -1\right]$$
Сумма $-1$ означает 2 голоса «аномалия», $-3$ — единогласно. Один «выброс» от одного алгоритма не даёт алерт.
### Доверенные процессы (TRUSTED_PROCESSES)
Для 30+ процессов (браузеры, Electron-приложения, IDE, медиаплееры) ML-классификация пропускается. Полный список:
```python
TRUSTED_PROCESSES = frozenset({
"firefox", "firefox-bin", "Web Content", "Isolated Web Co",
"chrome", "chromium", "chromium-browser",
"brave", "brave-browser", "opera",
"electron", "Electron",
"telegram", "Telegram", "telegram-desktop",
"slack", "discord", "discord-bin",
"code", "code-oss", "vscodium", "cursor",
"zoom", "zoom-bin", "zoomus",
"obsidian",
"spotify", "vlc", "mpv",
"libreoffice", "soffice",
"thunderbird",
"gnome-shell", "plasmashell", "kwin_wayland", "kwin_x11",
"Xorg", "Xwayland",
})
```
Пороговые алерты бэкенда (CPU > 80%, RAM > 3 ГБ, TCP > 100) работают для доверенных процессов.
### Обучающие данные
**Режим 1 — UNSW-NB15** (если `data/UNSW_NB15_training-set.csv` присутствует):
```python
df = pd.read_csv(DATASET_PATH)
numeric = df.select_dtypes(include=[np.number]).dropna()
X = numeric.iloc[:, :4].values.astype(np.float32)
```
Из датасета берутся первые 4 числовые колонки как приближение к вектору признаков.
**Режим 2 — Синтетические данные** (10 000 образцов, `n=10_000`):
| Признак | Сегмент | Доля | Распределение |
|---------|---------|------|---------------|
| CPU | Фоновые | 75% | Exponential(scale=0.3) |
| CPU | Лёгкие | 20% | Uniform[0.5, 15] |
| CPU | Тяжёлые | 5% | Uniform[15, 60] |
| RAM | Мелкие | 50% | Uniform[0.5, 50] МБ |
| RAM | Средние | 30% | Uniform[50, 300] МБ |
| RAM | Крупные | 12% | Uniform[300, 800] МБ |
| RAM | Десктоп | 8% | Uniform[800, 2000] МБ |
| Файлы | Немного | 70% | randint[0, 20) |
| Файлы | Много | 30% | randint[20, 80) |
| TCP | Мало | 70% | randint[0, 2) |
| TCP | Больше | 30% | randint[2, 20) |
### Сохранение и загрузка модели
```python
# Сохранение (joblib)
joblib.dump({
"iforest": self.iforest,
"lof": self.lof,
"ocsvm": self.ocsvm,
"scaler": scaler,
}, MODEL_PATH) # data/model.pkl
# Загрузка при старте (если pkl существует)
bundle = joblib.load(MODEL_PATH)
# legacy-проверка: если bundle не dict — переобучение
```
### Предварительное обучение (standalone)
```bash
cd agent
python train.py
# Done. Model saved to data/model.pkl (257000 training samples)
```
---
## 4. Серверная часть — API
### Стек
| Компонент | Версия | Роль |
|-----------|--------|------|
| FastAPI | ≥0.111 | ASGI-фреймворк, REST + WebSocket |
| aiosqlite | ≥0.20 | Асинхронный драйвер SQLite |
| uvicorn | ≥0.29 | ASGI-сервер |
| Pydantic v2 | встроен в FastAPI | Валидация входных данных |
OpenAPI документация: `http://localhost:8000/docs`
### REST-эндпоинты (полный список)
| Метод | Эндпоинт | Параметры | Описание |
|-------|----------|-----------|----------|
| `GET` | `/api/stats` | — | Агрегированная статистика для панели Overview |
| `GET` | `/api/metrics` | `limit=200` | Последние метрики процессов |
| `GET` | `/api/alerts` | `limit=50` | Последние алерты (с дедупликацией) |
| `GET` | `/api/file-events` | `limit=100` | Последние события файловой системы |
| `GET` | `/api/system-metrics` | `limit=60` | Системные CPU/RAM для графика (хронологически) |
| `GET` | `/api/active-anomalies` | — | Текущие аномальные процессы (last snapshot per PID) |
| `POST` | `/api/metrics` | тело: `MetricsBatch` | Приём батча от агента |
#### GET /api/stats — пример ответа
```json
{
"total_processes": 142,
"anomalies_today": 3,
"alerts_today": 1,
"cpu_avg": 18.4,
"ram_avg": 52.1
}
```
`cpu_avg` и `ram_avg` — средние по таблице `system_metrics` за сегодня.
#### GET /api/system-metrics — пример ответа
```json
[
{
"id": 1001,
"timestamp": "2025-05-28T14:29:55+00:00",
"cpu_percent": 15.2,
"ram_percent": 48.7,
"net_connections": 42
},
...
]
```
Возвращается в хронологическом порядке (ORDER BY id DESC → reversed) для прямой подачи в Recharts.
#### GET /api/active-anomalies — запрос к БД
```sql
SELECT m.*
FROM metrics m
INNER JOIN (
SELECT pid, MAX(id) AS max_id FROM metrics GROUP BY pid
) latest ON m.id = latest.max_id
WHERE m.is_anomaly = 1
ORDER BY m.cpu_percent DESC
LIMIT 20
```
Возвращает последний снимок каждого PID, у которого `is_anomaly=1`.
#### POST /api/metrics — логика обработки
1. Bulk INSERT всех процессов в `metrics`
2. INSERT одной строки системных метрик в `system_metrics`
3. Bulk INSERT событий ФС в `file_events`
4. **Пороговая проверка** (независимо от ML):
- `cpu_percent > 80` ИЛИ `memory_mb > 3000` ИЛИ `connections > 100`
- Такие процессы помечаются `is_anomaly=True` даже если ML не сработал
5. **Дедупликация алертов** (30-секундное окно):
- Если алерт с этим PID+именем был менее 30 секунд назад — `UPDATE alerts SET count=count+1`
- Иначе — `INSERT alerts` + `WS broadcast`
### Severity и Reason
```python
def _severity(proc: ProcessMetric) -> str:
if proc.cpu_percent > 80 or proc.memory_mb > 2000:
return "high"
if proc.cpu_percent > 50 or proc.memory_mb > 1000:
return "medium"
return "low"
def _reason(proc: ProcessMetric) -> str:
parts = []
if proc.cpu_percent > 80: parts.append(f"CPU {proc.cpu_percent:.1f}%")
if proc.memory_mb > 2000: parts.append(f"ОЗУ {proc.memory_mb:.0f} МБ")
if proc.connections > 50: parts.append(f"{proc.connections} TCP-соединений")
if proc.open_files > 200: parts.append(f"{proc.open_files} открытых файлов")
detail = ", ".join(parts) if parts else "статистический выброс (ансамбль ML: IF + LOF + OC-SVM)"
return f"Аномальное поведение: {detail}"
```
Если ни одного порогового признака нет — причина описывается как «статистический выброс ансамбля».
### WebSocket
```
GET /ws → WebSocket upgrade
```
Сервер хранит список подключённых клиентов в `_WsManager`. При обнаружении **новой** аномалии (прошедшей дедупликацию) рассылается:
```json
{
"type": "alert",
"pid": 4567,
"process_name": "python3",
"reason": "Аномальное поведение: CPU 95.2%",
"severity": "high",
"timestamp": "2025-05-28T14:30:05.123456+00:00"
}
```
Дедуплицированные алерты (инкремент `count`) **не** рассылаются по WS — клиент получает уведомление только при первом обнаружении.
Клиенты, разорвавшие соединение, автоматически удаляются из списка при следующей широковещательной рассылке.
### Pydantic-схемы
```python
class ProcessMetric(BaseModel):
pid: int
name: str
cpu_percent: float
memory_mb: float
open_files: int
connections: int
is_anomaly: bool = False
class SystemMetric(BaseModel):
cpu_percent: float
ram_percent: float
network_connections: int
class FileEvent(BaseModel):
path: str
event_type: str
timestamp: str
class MetricsBatch(BaseModel):
processes: list[ProcessMetric]
system: SystemMetric
file_events: list[FileEvent] = []
timestamp: str
```
---
## 5. База данных
SQLite, файл: `data/edr.db`. Четыре таблицы.
### metrics
```sql
CREATE TABLE metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL, -- ISO 8601 UTC
pid INTEGER,
name TEXT,
cpu_percent REAL, -- нормализовано на _CPU_COUNT
memory_mb REAL, -- RSS в МБ
open_files INTEGER,
connections INTEGER,
is_anomaly INTEGER DEFAULT 0 -- 0=норма, 1=аномалия (ML или порог)
);
```
### alerts
```sql
CREATE TABLE alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL, -- время первого обнаружения
pid INTEGER,
process_name TEXT,
reason TEXT, -- человекочитаемое описание
severity TEXT, -- low | medium | high
last_seen TEXT, -- время последнего повтора (для дедупликации)
count INTEGER DEFAULT 1 -- сколько раз алерт повторился
);
```
**Дедупликация:** при поступлении нового батча бэкенд ищет алерт с тем же PID и именем, у которого `last_seen >= NOW() - 30s`. Если найден — инкрементирует `count` и обновляет `last_seen`. Иначе создаёт новую запись.
### file_events
```sql
CREATE TABLE file_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
path TEXT,
event_type TEXT -- create | modify | delete
);
```
### system_metrics
```sql
CREATE TABLE system_metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
cpu_percent REAL, -- система в целом (psutil.cpu_percent)
ram_percent REAL, -- psutil.virtual_memory().percent
net_connections INTEGER -- psutil.net_connections() count
);
```
Одна строка на батч (каждые 5 секунд). Используется для графика нагрузки в реальном времени и для расчёта `cpu_avg`/`ram_avg` в `/api/stats`.
---
## 6. Веб-интерфейс
### Стек
| Технология | Версия | Назначение |
|-----------|--------|-----------|
| React | 18.3 | Декларативный UI-фреймворк |
| TypeScript | 5.4 | Статическая типизация |
| Vite | 5.3 | Сборщик, HMR |
| Tailwind CSS | 3.4 | Utility-first стили |
| Radix UI / shadcn/ui | 1.x | Headless-компоненты (доступность) |
| Recharts | 2.12 | Графики (обёртка над D3.js) |
| TanStack Query | 5.x | Серверный стейт, кэш, polling |
| Lucide React | последняя | Иконки |
### Структура компонентов
```
frontend/src/
├── App.tsx — корневой компонент, Tab-навигация
├── components/
│ ├── Overview.tsx — вкладка «Обзор»
│ ├── Processes.tsx — вкладка «Процессы»
│ ├── Alerts.tsx — вкладка «Алерты» + WS
│ ├── FileEvents.tsx — вкладка «События ФС»
│ ├── SystemChart.tsx — Recharts LineChart (CPU + RAM)
│ └── ui/ — shadcn/ui компоненты
├── hooks/
│ └── use-toast.ts — Toast-уведомления
└── lib/
└── api.ts — типы и fetcher-функции
```
### Вкладка Overview (Обзор)
Компонент: `components/Overview.tsx`
#### Карточки-счётчики (StatCard × 5)
| Заголовок | Источник | Цвет акцента |
|-----------|---------|-------------|
| Всего процессов | `stats.total_processes` | `#00d4ff` (cyan) |
| Аномалий сегодня | `stats.anomalies_today` | `#ff6b35` (orange) |
| Алертов сегодня | `stats.alerts_today` | `#ff3366` (red) |
| Средний CPU | `stats.cpu_avg` % | `#00ff9d` (green) |
| Средний RAM | `stats.ram_avg` % | `#a78bfa` (violet) |
#### Панель «Активные угрозы» (ActiveAnomaliesList)
Источник данных: `GET /api/active-anomalies` (polling 5 сек)
Отображает список процессов с `is_anomaly=1` в текущем снимке. Для каждого показывает:
- Имя процесса + PID
- CPU%, RAM МБ, TCP-соединения (цветные метки)
- Пульсирующая красная точка-индикатор
При отсутствии аномалий показывает зелёный чекмарк.
#### График системной нагрузки (SystemChart)
Источник: `GET /api/system-metrics?limit=60` (polling 5 сек)
Recharts `LineChart` с двумя линиями — CPU% и RAM%. Ось X — время (HH:MM:SS). Подписи и тултипы в стиле JetBrains Mono.
Располагается на 2/3 ширины рядом с «Активными угрозами».
#### Диаграмма серьёзности (SeverityDonut)
Источник: `GET /api/alerts?limit=200` (polling 10 сек)
Recharts `PieChart` (кольцо, innerRadius=48, outerRadius=72). Три сегмента:
| Сегмент | Цвет |
|---------|------|
| Высокий | `#ff3366` |
| Средний | `#ff6b35` |
| Низкий | `#00d4ff` |
#### Топ процессов по алертам (TopProcessesBar)
Источник: те же данные алертов, что и SeverityDonut.
Recharts `BarChart` (горизонтальный, layout="vertical"). Агрегирует `count` алертов по имени процесса, показывает топ-6 по убыванию. Цвет баров: `#ff6b35`.
### Вкладка Processes (Процессы)
Компонент: `components/Processes.tsx`
Источник: `GET /api/metrics?limit=200` (polling 5 сек)
Таблица «последний снимок» для каждого уникального PID (MAX(id) per PID), отсортированная по убыванию CPU%.
| Колонка | Значение |
|---------|---------|
| PID | числовой идентификатор |
| Имя | имя исполняемого файла |
| CPU% | нормализованная нагрузка |
| RAM МБ | RSS-память |
| TCP | число соединений |
| Файлы | число дескрипторов |
| Статус | зелёный «Normal» / красный «Anomaly» |
Строки с аномалиями: красная левая рамка (`border-l-2 border-red-500`), красный фон.
### Вкладка Alerts (Алерты)
Компонент: `components/Alerts.tsx`
Источник: `GET /api/alerts?limit=50` (polling 5 сек) + WebSocket `/ws`
Таблица алертов. Колонки: Время, PID, Процесс, Описание, Серьёзность, Счётчик.
Индикатор WS-подключения (зелёная/красная точка).
При получении WS-сообщения:
1. Принудительная инвалидация кэша `alerts` и `stats` через `queryClient.invalidateQueries`
2. Показ Toast-уведомления в правом нижнем углу
Severity-бейджи:
| Уровень | Цвет |
|---------|------|
| `high` | красный |
| `medium` | оранжевый |
| `low` | синий |
### Вкладка File Events (События ФС)
Компонент: `components/FileEvents.tsx`
Источник: `GET /api/file-events?limit=100` (polling 5 сек)
Таблица: Время, Путь, Тип события. Цветные бейджи типов:
| Тип | Цвет |
|-----|------|
| `create` | зелёный |
| `modify` | жёлтый |
| `delete` | красный |
### Дизайн-система
```
Фон: #0a0a0f (почти чёрный)
Поверхность: #111118
Акцент зелёный: #00ff9d (cyber green)
Акцент синий: #00d4ff (cyan)
Акцент красный: #ff3366
Шрифт: JetBrains Mono (monospace)
Стиль: SOC-dashboard, тёмный, декоративная сетка
```
### TypeScript-типы (frontend/src/lib/api.ts)
```typescript
interface ProcessMetric {
id: number
timestamp: string
pid: number
name: string
cpu_percent: number
memory_mb: number
open_files: number
connections: number
is_anomaly: number // 0 | 1 (SQLite INTEGER, не boolean)
}
interface Alert {
id: number
timestamp: string
last_seen: string | null
count: number
pid: number
process_name: string
reason: string
severity: 'low' | 'medium' | 'high'
}
interface SystemMetricRow {
id: number
timestamp: string
cpu_percent: number
ram_percent: number
net_connections: number
}
```
### Polling-интервалы
| Данные | Интервал | Эндпоинт |
|--------|---------|---------|
| Stats | 5 сек | `/api/stats` |
| System metrics | 5 сек | `/api/system-metrics` |
| Active anomalies | 5 сек | `/api/active-anomalies` |
| Processes | 5 сек | `/api/metrics` |
| Alerts (polling) | 5 сек | `/api/alerts` |
| Alerts (push) | мгновенно | WebSocket `/ws` |
| File events | 5 сек | `/api/file-events` |
---
## 7. Развёртывание (Docker)
### docker-compose.yml
```yaml
services:
backend:
build: ./backend
volumes:
- ./data:/app/data # SQLite + model.pkl
ports:
- "8000:8000"
environment:
- DB_PATH=/app/data/edr.db
healthcheck:
test: python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/stats')"
interval: 10s
timeout: 5s
retries: 10
agent:
build: ./agent
volumes:
- ./data:/app/data # общий том с backend
environment:
- BACKEND_URL=http://backend:8000
- COLLECT_INTERVAL=5
- MODEL_PATH=/app/data/model.pkl
- DATASET_PATH=/app/data/UNSW_NB15_training-set.csv
depends_on:
backend:
condition: service_healthy
pid: host # видит PID-пространство хоста
privileged: true # требуется для psutil.net_connections()
frontend:
build: ./frontend # multi-stage: node build → nginx
ports:
- "3000:3000"
depends_on:
- backend
```
**Ключевые особенности агентского контейнера:**
- `pid: host` — агент видит реальные процессы хоста (без этого будут только контейнерные PID)
- `privileged: true``psutil.net_connections()` требует root для чтения `/proc/net/tcp`
- Зависимость от `service_healthy` бэкенда — агент стартует только после того, как `/api/stats` возвращает 200
### Запуск
```bash
# Стандартный запуск
docker compose up --build
# С датасетом UNSW-NB15
cp /path/to/UNSW_NB15_training-set.csv data/
docker compose up --build
# Без Docker (разработка)
cd backend && uvicorn main:app --reload --port 8000
cd agent && python main.py
cd frontend && pnpm dev
```
---
## 8. Конфигурация и переменные окружения
### Агент (`agent/main.py`)
| Переменная | По умолчанию | Описание |
|-----------|-------------|---------|
| `BACKEND_URL` | `http://localhost:8000` | Адрес бэкенда |
| `COLLECT_INTERVAL` | `5` | Интервал сбора метрик (секунды) |
### Модель (`agent/model.py`)
| Переменная | По умолчанию | Описание |
|-----------|-------------|---------|
| `MODEL_PATH` | `data/model.pkl` | Путь для сохранения/загрузки ансамбля |
| `DATASET_PATH` | `data/UNSW_NB15_training-set.csv` | Путь к датасету для обучения |
### Бэкенд (`backend/main.py`)
| Переменная | По умолчанию | Описание |
|-----------|-------------|---------|
| `DB_PATH` | `../data/edr.db` | Путь к SQLite-базе данных |
---
## 9. Расхождения с дипломным документом
В файле `diploma_practical_section.md` обнаружены следующие расхождения с реальной кодовой базой:
### 9.1 Таблица Overview устарела
В документе:
> «Пять карточек-счётчиков» + «График реального времени»
В коде (после коммита `4253552`):
- `ActiveAnomaliesList` — живой список текущих аномальных процессов
- `SeverityDonut` — кольцевая диаграмма распределения алертов по серьёзности
- `TopProcessesBar` — горизонтальный bar-chart топ-6 процессов по числу алертов
Все три компонента опрашивают `/api/active-anomalies` и `/api/alerts`.
### 9.2 Отсутствует таблица system_metrics
Документ описывает 3 таблицы. Реально их 4: `metrics`, `alerts`, `file_events`, **`system_metrics`**.
### 9.3 Отсутствуют колонки alerts.last_seen и alerts.count
Документ не упоминает дедупликацию алертов. Реально таблица `alerts` содержит `last_seen TEXT` и `count INTEGER DEFAULT 1`. Дедупликационное окно — 30 секунд.
### 9.4 Отсутствуют два API-эндпоинта
Документ описывает 5 эндпоинтов. Реально их 7:
- `GET /api/system-metrics` — данные для графика нагрузки
- `GET /api/active-anomalies` — текущие аномальные процессы
### 9.5 Не описана нормализация CPU
Агент делит `cpu_percent` на `psutil.cpu_count(logical=True)`. Без этого на многоядерных хостах значения CPU > 100% приводят к ложным алертам.
### 9.6 WebSocket срабатывает только на новые алерты
Документ: «при поступлении батча с аномальными процессами бэкенд транслирует каждому клиенту JSON».
Реально: WS-сообщение отправляется **только** при `INSERT alerts` (первое обнаружение). Повторные срабатывания в течение 30 секунд дают только `UPDATE count` без WS-рассылки.
### 9.7 Порог для включения RAM в reason
Документ: `RAM > 2 ГБ``severity=high`.
Реально `_severity()` проверяет `memory_mb > 2000` для `"high"`, но `_reason()` добавляет строку ОЗУ тоже при `memory_mb > 2000`. Пороговый алерт (без ML) срабатывает при `memory_mb > 3000`. Документ эту разницу не разграничивает.
---
*Справочник отражает состояние кодовой базы на коммит `4253552` (ветка master).*