882 lines
35 KiB
Markdown
882 lines
35 KiB
Markdown
# Технический справочник системы 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).*
|