Agent-style report body for SAC aggregation; shared stats cards; RDP ban note in docs Co-authored-by: Cursor <cursoragent@cursor.com>
15 KiB
Интеграция агентов с SAC
Контракт между ssh-monitor, RDP-login-monitor и Security Alert Center.
Изменения вносятся в репозитории агентов отдельными задачами.
1. Режим UseSAC
1.1. Параметры конфигурации
ssh-monitor (/etc/ssh-monitor.conf):
# 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):
$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; HTTPGET {base}/healthOK.--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 — sac-api.env или UI Настройки → Правило оповещений: один порог severity и выбор каналов (Telegram / webhook / email).
NOTIFY_MIN_SEVERITY=warning
NOTIFY_CHANNELS=telegram,webhook,email
TELEGRAM_ENABLED=true
TELEGRAM_BOT_TOKEN=<bot>
TELEGRAM_CHAT_ID=<chat>
| Severity события | NOTIFY_MIN_SEVERITY=warning |
high |
|---|---|---|
info (успешный RDP/SSH login) |
нет | нет |
warning (*.login.failed, sudo) |
да | нет |
high / critical (ban, problem) |
да | да |
Events и problems используют одно глобальное правило (notification_policy в БД, миграция 007).
Telegram из SAC отправляется с parse_mode=HTML (как у RDP-login-monitor): для rdp.login.* — пользователь, IP, logon_type, рабочая станция, Event ID Windows; для ssh.login.* и privilege.sudo.command — поля из details.
UI Настройки (/settings) — правило + параметры каналов (JWT admin). После деплоя: alembic upgrade head.
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. Запрос
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
{ ... событие по event-schema-v1.json ... }
2.2. Ответ
| HTTP | Значение | status в теле |
created |
|---|---|---|---|
| 201 | Событие записано впервые | created |
true |
| 409 | Тот же event_id уже есть (идемпотентность) |
duplicate |
false |
| 422 | Ошибка JSON Schema | — | — |
Пример 201:
{
"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 при ошибке
- Записать JSON в
SAC_SPOOL_DIR/{event_id}.json. - ssh-monitor: каждую итерацию цикла вызывается
sac_flush_spool(до 20 файлов). - После успеха (HTTP 201 или 409) — удалить файл из spool.
- Лимит размера 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 |
warning–critical* |
| logind new/removed/failed | session.logind.* |
info–warning |
| Ежедневный отчёт | 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_attemptssudo:run_as,command,pwd,risk_levelban:ban_until,enable_ip_ban(SSH / ipset)- RDP-login-monitor: автобан IP в схеме пока не стандартизирован (
rdp.ip.bannedзарезервирован в SAC для будущего); в отчёте Windows строка «Активных банов» = 0 или число событийrdp.ip.banned, если агент начнёт слать brute:window_sec,fails_in_windowwhitelist_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_namegateway_target,gateway_error_codefiltered_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:
"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 (события, по умолчанию 90 с) и по fingerprint problem (300 с). Таблица notification_cooldown, env: SAC_NOTIFY_EVENT_COOLDOWN_SEC, SAC_NOTIFY_PROBLEM_COOLDOWN_SEC. В логах API: notify cooldown skip.
5. Точки встраивания в код агентов
ssh-monitor
- Новая функция
send_sac_event()вызывается из мест, где сейчасnotify_send "$message". - Обёртка
notify_or_sac():UseSAC=off→notify_sendexclusive→ только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.
- При
UseSAC=exclusiveсуточный отчёт может формироваться на SAC (generated_by: sacвdetails), если агент за день не прислалreport.daily.*— см.SAC_DAILY_REPORT_*, jobpython -m app.jobs.daily_report, timersac-daily-report.timer.
6. Обратная совместимость
- По умолчанию
UseSAC=off— поведение 100% как сейчас. BACKUP_WEBHOOK_URLв ssh-monitor приexclusiveне используется для обычных алертов (только emergency вfallback— опционально).
7. Чеклист готовности агента
- Параметры конфига задокументированы в README агента (ssh-monitor, RDP-login-monitor)
--check-sac/Test-SacConnection/-CheckSac- Spool и повторная отправка
- Все типы из п. 3 покрыты (RDP: основные; 4648 — при появлении обработки)
event_idUUID на каждое событие- Секреты не в git
8. См. также
- event-schema-v1.json
- TZ.md §3.2, §4.8