diff --git a/docs/diploma_practical_section.md b/docs/diploma_practical_section.md index acde5c7..78bddfc 100644 --- a/docs/diploma_practical_section.md +++ b/docs/diploma_practical_section.md @@ -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% — 50–300 МБ, 12% — 300–800 МБ, 8% — 800–2000 МБ (браузеры, IDE); - Файлы: 70% — 0–20 дескрипторов, 30% — 20–80; diff --git a/docs/technical_reference.md b/docs/technical_reference.md new file mode 100644 index 0000000..79a38f4 --- /dev/null +++ b/docs/technical_reference.md @@ -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).*