Files
security-alert-center/docs/agent-integration.md
T
PTah 79a78dc7c9 feat: Telegram settings in DB with UI edit and test send
Store notification_channels (migration 004), effective config DB over env,
PUT/test API endpoints, Settings form, and pass db session into ingest notify.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 16:05:32 +10:00

282 lines
14 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.
# Интеграция агентов с SAC
Контракт между **ssh-monitor**, **RDP-login-monitor** и **Security Alert Center**.
Изменения вносятся в репозитории агентов отдельными задачами.
---
## 1. Режим `UseSAC`
### 1.1. Параметры конфигурации
**ssh-monitor** (`/etc/ssh-monitor.conf`):
```ini
# off | exclusive | dual | fallback
UseSAC="exclusive"
SAC_URL="https://sac.kalinamall.ru/api/v1/events"
SAC_API_KEY="sac_xxxxxxxx"
SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool"
SAC_SEND_HEARTBEAT="1"
SAC_FALLBACK_FAILURES="5"
SAC_TIMEOUT_SEC="12"
```
**RDP-login-monitor** (`Login_Monitor.ps1` или отдельный `.conf`):
```powershell
$UseSAC = "exclusive" # off | exclusive | dual | fallback
$SacUrl = "https://sac.kalinamall.ru/api/v1/events"
$SacApiKey = "sac_xxxxxxxx"
$SacSpoolDir = "D:\Soft\Logs\sac-spool"
```
### 1.2. Матрица поведения
| Событие | `off` | `exclusive` | `dual` | `fallback` |
|---------|-------|-------------|--------|------------|
| Auth / sudo / ban / RDP login | Telegram/email с агента | Только SAC JSON | SAC + Telegram | SAC; при сбоях → Telegram |
| Daily report | С агента | SAC формирует и шлёт | Оба | SAC, fallback как выше |
| Heartbeat | С агента (если включён) | Только SAC (`agent.heartbeat`) | Оба | SAC |
| Локальный LOG_FILE | Да | Да | Да | Да |
### 1.3. Проверка при старте
- `UseSAC=off` — без изменений: хотя бы один канал `NOTIFY_CHAIN` (как сейчас).
- `UseSAC≠off` — обязательны `SAC_URL`, `SAC_API_KEY`; HTTP `GET {base}/health` OK.
- `--check-sac` / `Test-SacConnection` — отправка `agent.test`, ожидание **HTTP 201** (повтор с тем же `event_id`**409**, тоже успех для spool).
### 1.3.1. Telegram и email при `UseSAC=exclusive`
При **`exclusive`** агент **не** шлёт Telegram/email — оператору нужны оповещения **из SAC** (`backend/app/services/telegram_notify.py`).
На сервере SAC в **`/opt/security-alert-center/config/sac-api.env`** (см. `deploy/env.native.example`):
```env
TELEGRAM_ENABLED=true
TELEGRAM_BOT_TOKEN=<bot>
TELEGRAM_CHAT_ID=<chat>
# warning — failed login, sudo, problems; high — только high/critical
TELEGRAM_MIN_SEVERITY=warning
```
| Severity события | `warning` | `high` (по умолчанию в example) |
|------------------|-----------|----------------------------------|
| `info` (успешный RDP/SSH login) | нет | нет |
| `warning` (`*.login.failed`, sudo) | **да** | нет |
| `high` / `critical` (ban, problem) | **да** | **да** |
Problems (`notify_problem`) учитывают тот же порог `TELEGRAM_MIN_SEVERITY`.
UI **Настройки** (`/settings`) — просмотр и редактирование Telegram (JWT admin): значения в БД `notification_channels` с fallback на `sac-api.env`, пока запись не создана. После деплоя выполнить `alembic upgrade head` на сервере SAC.
### 1.4. Версии и доставка обновлений
При **любом** изменении агента, влияющем на SAC или поведение на хосте, поднимайте версию и пушьте в **git.kalinamall.ru**:
| Агент | Маркер версии | Как хост узнаёт о новой версии |
|-------|---------------|--------------------------------|
| **ssh-monitor** | `SSH_MONITOR_VERSION` в `ssh-monitor`; `# SAC client release:` в `sac-client.sh` | `update_ssh_monitor.sh`: `git pull` в клоне → сравнение sha256 `ssh-monitor` и `sac-client.sh` |
| **RDP-login-monitor** | `$ScriptVersion` в `Login_Monitor.ps1` и **та же** строка в `version.txt` на шаре NETLOGON | `Deploy-LoginMonitor.ps1`: сверка `version.txt` и SHA256 пакета (`Login_Monitor.ps1`, `Sac-Client.ps1`); при отсутствии SAC в settings — **`UseSAC=dual`** из example; подсказка `# $ServerDisplayName` |
**Кириллица в SAC (RDP):** до **1.2.8-SAC** `Invoke-WebRequest` мог слать JSON не в UTF-8 — в UI «Отчёты» summary вида `RDP 24?: ??????` вместо `RDP 24ч: сессий …`. Обновите `Sac-Client.ps1` на всех хостах; старые события в БД не пересчитываются, исправятся только новые ingest после деплоя.
**HTTP 422 и spool (RDP):** до **1.2.9-SAC** при отклонении схемой (часто `title` длиннее 256 символов) JSON попадал в `sac-spool` и повторялся бесконечно. С **1.2.9-SAC**: обрезка `title`/`summary`, UTF-8 POST, при 422 файл уходит в `sac-spool/rejected/` (не ретраится). Старые `.json` в spool на хосте удалите или перенесите вручную после обновления.
**HTTP 409 и spool (RDP):** в PowerShell `Invoke-WebRequest` часто **бросает исключение** на 409 (и иногда на 201), хотя событие уже в SAC. До **1.2.10-SAC** клиент считал это ошибкой и снова слал тот же `event_id` из spool каждые ~5 с (лог `WARN: SAC POST HTTP 409`). С **1.2.10-SAC** коды **201/409/202** обрабатываются как успех и файл spool удаляется.
**HTTP 422 на `agent.lifecycle` (RDP):** часто из‑за битого JSON с кириллицей в `summary` (`ConvertTo-Json` + неверная кодировка POST). С **1.2.11-SAC** — сериализация через `JavaScriptSerializer` (Unicode `\uXXXX`), в лог пишется тело ответа SAC (`schema_errors`). Файлы в `sac-spool/rejected/` после 422 можно удалить.
**422 `json_invalid` / `Extra data` (позиция 4):** тело POST было `null` + JSON (утечка в pipeline от `Write-Log`). С **1.2.12-SAC**: `[void](Write-Log)`, одна строка JSON, без `ConvertFrom-Json` перед POST.
Только правки `Sac-Client.ps1` / `sac-client.sh`: для RDP достаточно поднять patch в `version.txt` (и при необходимости `$ScriptVersion`); для Linux `sac-client.sh` обновится по checksum даже без смены `SSH_MONITOR_VERSION`, но **рекомендуется** поднимать обе метки для логов.
### 1.5. Правила Problems v1 (SAC backend)
| `rule_id` | Условие | Пороги (env) |
|-----------|---------|--------------|
| `rule:brute_force_burst` | `ssh.login.failed` / `rdp.login.failed`, один `source_ip` | `SAC_BRUTE_FORCE_WINDOW_MINUTES=15`, `SAC_BRUTE_FORCE_THRESHOLD=30` |
| `rule:privilege_spike` | `privilege.sudo.command` на хосте | `SAC_PRIVILEGE_SPIKE_WINDOW_MINUTES=10`, `SAC_PRIVILEGE_SPIKE_THRESHOLD=10` |
| `rule:host_silence` | был heartbeat, но устарел (`agent.heartbeat`) | `SAC_HEARTBEAT_STALE_MINUTES` (как для UI «Хосты») |
Свежий `agent.heartbeat` **автоматически resolve** open/ack проблему `rule:host_silence`. Одиночные `high`/`critical` (ban, mass bruteforce) — как раньше.
---
## 2. Протокол ingest
### 2.1. Запрос
```http
POST /api/v1/events HTTP/1.1
Host: sac.kalinamall.ru
Content-Type: application/json
Authorization: Bearer sac_xxxxxxxx
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
```
### 2.2. Ответ
| HTTP | Значение | `status` в теле | `created` |
|------|----------|-----------------|-----------|
| **201** | Событие записано впервые | `created` | `true` |
| **409** | Тот же `event_id` уже есть (идемпотентность) | `duplicate` | `false` |
| **422** | Ошибка JSON Schema | — | — |
Пример **201**:
```json
{
"status": "created",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"created": true,
"sac_event_url": "https://sac.kalinamall.ru/api/v1/events/12345",
"problem_id": null
}
```
`event_id` — обязательный **UUID** (RFC 4122), уникален глобально; в БД индекс `UNIQUE (event_id)`.
### 2.3. Spool при ошибке
1. Записать JSON в `SAC_SPOOL_DIR/{event_id}.json`.
2. **ssh-monitor:** каждую итерацию цикла вызывается `sac_flush_spool` (до 20 файлов).
3. После успеха (**HTTP 201** или **409**) — удалить файл из spool.
4. Лимит размера spool (например 500 MB) — логировать WARN, не удалять без алерта.
---
## 3. Маппинг уведомлений → типы событий
### 3.1. ssh-monitor
| Текущий текст (сокращённо) | `type` | `severity` |
|----------------------------|--------|------------|
| Успешное SSH | `ssh.login.success` | info |
| Неудачная SSH | `ssh.login.failed` | warning |
| IP заблокирован | `ssh.ip.banned` | high |
| Лимит без бана | `ssh.ip.bruteforce.threshold` | warning |
| Массовый брутфорс | `ssh.bruteforce.mass` | high |
| Sudo | `privilege.sudo.command` | warningcritical* |
| logind new/removed/failed | `session.logind.*` | infowarning |
| Ежедневный отчёт | `report.daily.ssh` | info |
| Heartbeat | `agent.heartbeat` | info |
\* critical — по эвристике команды (`useradd`, `passwd`, `rm -rf`, …) в `details.risk_level`.
**Дополнительные поля в `details` (exclusive):**
- `user`, `source_ip`, `port`, `attempt_number`, `max_attempts`
- `sudo`: `run_as`, `command`, `pwd`, `risk_level`
- `ban`: `ban_until`, `enable_ip_ban`
- `brute`: `window_sec`, `fails_in_window`
- `whitelist_matched`: boolean
### 3.2. RDP-login-monitor
| Событие | `type` | `severity` |
|---------|--------|------------|
| 4624 успех | `rdp.login.success` | info |
| 4625 неудача | `rdp.login.failed` | warning |
| 4648 | `auth.explicit.credentials` | warning |
| RD Gateway 302 | `rdg.connection.success` | info |
| RD Gateway 303 | `rdg.connection.failed` | warning |
| Старт/стоп | `agent.lifecycle` | info |
| Отчёт | `report.daily.rdp` | info |
**Дополнительные поля:**
- `event_id_windows`, `logon_type`, `ip_address`, `workstation_name`
- `gateway_target`, `gateway_error_code`
- `filtered_out`, `filter_reason`
---
## 3.3. Человекочитаемое имя хоста (`host.display_name`)
В UI SAC (**Хосты**, фильтры Problems/Events) показывается `display_name`, если оно задано; иначе `hostname` из ОС.
| Агент | Параметр | В ingest |
|-------|----------|--------|
| **ssh-monitor** | `SERVER_DISPLAY_NAME` в `/etc/ssh-monitor.conf` | `host.display_name` (пусто — поле не передаётся) |
| **RDP-login-monitor** | `$ServerDisplayName` в `login_monitor.settings.ps1` | `host.display_name`; `hostname` = `$env:COMPUTERNAME` |
Пример фрагмента JSON:
```json
"host": {
"hostname": "NEW-ADMIN-PC",
"display_name": "UNMS Kalina",
"os_family": "windows"
}
```
Telegram у ssh/RDP использует ту же подпись, что и `display_name`, когда параметр задан.
### Версии агентов (единый номер)
| Репозиторий | Источник версии | SAC `product_version` |
|-------------|-----------------|------------------------|
| **ssh-monitor** | `SSH_MONITOR_VERSION` в `ssh-monitor` + `version.txt` | из `SSH_MONITOR_VERSION` |
| **RDP-login-monitor** | `$ScriptVersion` в `Login_Monitor.ps1` + `version.txt` | из `$ScriptVersion` |
Не задавайте разные номера для «скрипта» и «SAC-модуля» — в UI **Хосты** отображается одна версия с ingest.
---
## 4. Поля `dedup_key` (рекомендации)
| Тип | Формат dedup_key |
|-----|------------------|
| ssh.login.success | `{product}\|{host}\|ssh.login.success\|{user}\|{ip}` |
| ssh.login.failed | `{product}\|{host}\|ssh.login.failed\|{ip}` (окно в SAC) |
| privilege.sudo | `{product}\|{host}\|sudo\|{user}\|{hash(command)}` |
| rdp.login.failed | `{product}\|{host}\|rdp.failed\|{ip}\|{user}` |
SAC применяет cooldown по `dedup_key` + правилам.
---
## 5. Точки встраивания в код агентов
### ssh-monitor
- Новая функция `send_sac_event()` вызывается из мест, где сейчас `notify_send "$message"`.
- Обёртка `notify_or_sac()`:
- `UseSAC=off``notify_send`
- `exclusive` → только `send_sac_event``summary` — тот же текст)
- `dual` → оба
- `fallback``send_sac_event`; при fail increment counter → при пороге `notify_send`
### RDP-login-monitor
- `Send-SacEvent` + замена вызовов `Send-TelegramMessage` для событий мониторинга.
- Heartbeat и daily report — отдельные типы в SAC.
---
## 6. Обратная совместимость
- По умолчанию `UseSAC=off` — поведение 100% как сейчас.
- `BACKUP_WEBHOOK_URL` в ssh-monitor при `exclusive` не используется для обычных алертов (только emergency в `fallback` — опционально).
---
## 7. Чеклист готовности агента
- [x] Параметры конфига задокументированы в README агента (ssh-monitor, RDP-login-monitor)
- [x] `--check-sac` / `Test-SacConnection` / `-CheckSac`
- [x] Spool и повторная отправка
- [ ] Все типы из п. 3 покрыты (RDP: основные; 4648 — при появлении обработки)
- [ ] `event_id` UUID на каждое событие
- [ ] Секреты не в git
---
## 8. См. также
- [event-schema-v1.json](event-schema-v1.json)
- [TZ.md](TZ.md) §3.2, §4.8