Files

19 KiB
Raw Permalink Blame History

Интеграция агентов с 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; HTTP GET {base}/health OK.
  • --check-sac / Test-SacConnection — отправка agent.test, ожидание HTTP 201 (повтор с тем же event_id409, тоже успех для 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 или поведение на хосте, поднимайте версию и пушьте в GitHub (github.com/PTah); для зеркала kalinamall — .\scripts\Push-Mirror.ps1 kalinamall:

Агент Маркер версии Как хост узнаёт о новой версии
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 «Хосты»)

Proactive host_silence: фоновый scan в sac-api (SAC_HOST_SILENCE_SCAN_ENABLED, интервал SAC_HOST_SILENCE_SCAN_INTERVAL_MINUTES, по умолчанию каждые 5 мин) создаёт Problem и шлёт Telegram без ожидания нового ingest с хоста. Альтернатива: systemd sac-host-silence-scan.timer + python -m app.jobs.host_silence_scan (отключите in-process scan при нескольких воркерах uvicorn).

Свежий 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 при ошибке

  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 (SSH / ipset)
  • RDP-login-monitor: автобан IP в схеме пока не стандартизирован (rdp.ip.banned зарезервирован в SAC для будущего); в отчёте Windows строка «Активных банов» = 0 или число событий rdp.ip.banned, если агент начнёт слать
  • 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
4634 / 4647 выход (прямой RDP, только рабочая станция) rdp.session.logoff info
RCM 20506 Shadow Control started rdp.shadow.control.started warning
RCM 20507 Shadow Control stopped rdp.shadow.control.stopped warning
RCM 20510 Shadow Control permission rdp.shadow.control.permission warning
WinRM 91 inbound shell (Enter-PSSession) winrm.session.started warning
Security 5140 admin share (C$, ADMIN$) smb.admin_share.access 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
Инвентаризация железа/ПО agent.inventory info (SAC: warning, если изменилось железо)

Инвентаризация (agent.inventory, RDP ≥ 2.0.21-SAC): агент шлёт снимок в details.inventory (CPU, RAM, диски, GPU, Windows, IPv4). Интервал по умолчанию 12 ч; отключение: $GetInventory = $false в login_monitor.settings.ps1. SAC хранит последний снимок в hosts.inventory; при изменении железа ingest повышает severity до warning и шлёт оповещение.

Дополнительные поля:

  • event_id_windows, logon_type, ip_address, workstation_name
  • Shadow: shadower_user, target_user, session_id, shadow_mode, shadow_action
  • WinRM: source_ip, resource_uri, transport
  • Admin share 5140: share_name, share_path, relative_target, access_mask
  • gateway_target, gateway_error_code
  • filtered_out, filter_reason

Переключатели в login_monitor.settings.ps1 (≥ 1.2.23-SAC): $EnableRcmShadowControlMonitoring, $EnableWinRmInboundMonitoring, $EnableAdminShareMonitoring (по умолчанию 1; Security 5140, audit File Share). $GetInventory (по умолчанию $true) — опрос железа/ПО для SAC. Подавление: ignore.lst с префиксами shadow:, winrm:, smb: / 5140:.

Exchange (RDP ≥ 2.0.23-SAC): на почтовом сервере $WinRmExchangeStrictMode = 1 — WinRM 91 без user в EventData не уходит в SAC; корреляция 4624 только при LogonProcess WinRM (отсекает ложные связки с Outlook/LT3).


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=offnotify_send
    • exclusive → только send_sac_eventsummary — тот же текст)
    • dual → оба
    • fallbacksend_sac_event; при fail increment counter → при пороге notify_send

RDP-login-monitor

  • Send-SacEvent + замена вызовов Send-TelegramMessage для событий мониторинга.
  • Heartbeat и daily report — отдельные типы в SAC.
  • Команды SAC (≥ 2.1.0-SAC): poll GET /api/v1/agent/commands?agent_instance_id=… каждые $SacCommandPollIntervalSec (по умолчанию 60 с); типы qwinsta, logoff. Ответ: POST /api/v1/agent/commands/{uuid}/result. Учётные данные доменного admin приходят в run_as (на SAC — SAC_WIN_ADMIN_*). Подробнее: agent-control-plane.md.
  • При UseSAC=exclusive суточный отчёт может формироваться на SAC (generated_by: sac в details), если агент за день не прислал report.daily.* — см. SAC_DAILY_REPORT_*, job python -m app.jobs.daily_report, timer sac-daily-report.timer.
  • Единый шаблон отчёта (SSH / Windows): агенты ssh-monitor ≥ 1.2.8-SAC, RDP-login-monitor ≥ 1.2.21-SAC и SAC используют одинаковые секции в report_body / details.stats.
  • Только SAC, без отчёта с агента: на агенте DAILY_REPORT_ENABLED=0 (ssh) или $DailyReportEnabled = $false (RDP); на SAC SAC_DAILY_REPORT_ENABLED=true, SAC_DAILY_REPORT_SKIP_IF_AGENT_SENT=true.

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_id UUID на каждое событие
  • Секреты не в git

8. См. также