feat: update docs to actual state

This commit is contained in:
kilyabin
2026-05-29 17:36:21 +04:00
parent 42535528eb
commit 1a2a0dfbb7
2 changed files with 882 additions and 1 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ $$x' = \frac{x - \text{median}(X)}{\text{IQR}(X)}$$
**Режим 1 (основной).** Если в директории `data/` находится файл `UNSW_NB15_training-set.csv`, все три модели обучаются на реальных данных набора UNSW-NB15. Датасет UNSW-NB15 (University of New South Wales, 2015) содержит 257 000 записей сетевого трафика, включая нормальную активность и 9 типов атак (Fuzzers, Analysis, Backdoors, DoS, Exploits, Generic, Reconnaissance, Shellcode, Worms). Из набора извлекаются первые четыре числовые колонки как приближение к вектору признаков.
**Режим 2 (резервный).** При отсутствии датасета генерируется синтетическая обучающая выборка из 2000 образцов нормального поведения процессов с реалистичными распределениями:
**Режим 2 (резервный).** При отсутствии датасета генерируется синтетическая обучающая выборка из 10000 образцов нормального поведения процессов с реалистичными распределениями:
- CPU: 75% экспоненциальное распределение ≈ 0% (фоновые процессы), 20% Uniform[0.5, 15%] (лёгкие задачи), 5% Uniform[15, 60%] (тяжёлые процессы);
- RAM: 50% — 0.5–50 МБ (мелкие демоны), 30% — 50300 МБ, 12% — 300800 МБ, 8% — 8002000 МБ (браузеры, IDE);
- Файлы: 70% — 0–20 дескрипторов, 30% — 20–80;
+881
View File
@@ -0,0 +1,881 @@
# Технический справочник системы 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).*