Files
security-alert-center/docs/agent-control-plane.md
T
PapaTramp d7bbcc5337 docs: WinRM RDP update flow and SAC 0.20.18 deployment notes
Обновлены README и руководства по fallback WinRM (git→zip→клиент), SAC_PUBLIC_URL и краткому 502 при деплое.
2026-06-20 20:03:57 +10:00

328 lines
15 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.
# Agent Control Plane — концепция SAC v0.7+
**Статус:** утверждено для реализации (2026-06).
**Связано:** [roadmap.md](roadmap.md) §v0.7, [agent-integration.md](agent-integration.md), [agent-update-backlog.md](agent-update-backlog.md).
---
## 1. Зачем
Сейчас SAC — **односторонний** канал: агент → ingest. Для задач из [roadmap.md](roadmap.md) (§ «Добавить») нужен **обратный канал** SAC ↔ агент и управление хостами:
| # | Задача | Кратко |
|---|--------|--------|
| П.1 | RDG flap 302→303 | Детект, Problem, кнопка **qwinsta** / **logoff** в UI |
| П.2 | Обновления агентов | Режим GPO или SAC (pull + fallback SSH/WinRM) |
| П.3 | Настройки агента | Desired config в карточке хоста → poll агента |
**Эталонный git для prod и агентов:** [git.kalinamall.ru/PapaTramp](https://git.kalinamall.ru/PapaTramp). GitHub — публичная sanitized-копия; mirror-скрипты (`push-mirror.sh`, `Rewrite-GitHostUrls.ps1`) живут на kalinamall и нужны только для синхронизации URL в docs между remotes, к runtime агентов не относятся.
---
## 2. Общая архитектура
```mermaid
flowchart TB
subgraph sac [SAC Ubuntu]
API[FastAPI]
UI[Vue SPA]
PG[(PostgreSQL)]
VAULT[Encrypted secrets]
end
subgraph win [Windows host]
RDP[RDP-login-monitor]
end
subgraph linux [Linux host]
SSH[ssh-monitor]
end
RDP -->|POST events / heartbeat| API
SSH -->|POST events / heartbeat| API
RDP -->|GET agent/commands poll| API
SSH -->|GET agent/commands poll| API
UI --> API
API --> PG
API --> VAULT
```
**Принципы:**
- Агент за NAT — только **исходящий HTTPS**; inbound на хост не требуется.
- Команды и конфиг — **poll** на heartbeat (или отдельный timer 30–60 с).
- Секреты (admin password, SSH private keys, WinRM password) — **encrypted at rest** в PostgreSQL; master key в `SAC_HOST_KEY_ENCRYPTION_KEY` (env).
- Audit: все команды и результаты — события ingest `agent.command.*`, `agent.update.*`.
---
## 3. П.1 — RDG flap 302→303 и qwinsta / logoff
### 3.1. Смысл проблемы
При неудачном RDP через RD Gateway типична пара событий Windows **302 → 303** за несколько секунд. Клиент не удерживает сессию; **«зависшая» сессия обычно остаётся на компьютере пользователя** (session host / рабочая станция из `details.internal_ip`), а не на gateway.
SAC уже принимает:
| Windows | SAC `type` | Пример severity |
|---------|------------|-----------------|
| 302 | `rdg.connection.success` | info / warning |
| 303 | `rdg.connection.disconnected` | info |
| 303 (alt) | `rdg.connection.failed` | warning |
Правило должно учитывать **оба** варианта 303.
### 3.2. Правило детекта `rule:rdg_session_flap`
```
WHEN event_B.type IN (rdg.connection.disconnected, rdg.connection.failed)
AND exists event_A within 1..10 seconds BEFORE event_B
AND event_A.type = rdg.connection.success
AND same host_id
AND same details.user
AND (recommended) same details.internal_ip
OPTIONAL reduce false positives:
AND event_B.details.session_duration_sec <= 15 # если агент передаёт из 303
AND/OR low bytes transferred in 303 payload
THEN:
mark event_B.details.rdg_flap = true
create Problem rule:rdg_session_flap (dedup 30 s, см. ниже)
send notification (Telegram / email / webhook по policy)
```
**Порядок:** только **302 → 303**, не наоборот.
**Окно:** **110 секунд** между 302 и 303.
**Dedup Problem:** **30 секунд** по ключу `{host_id}|{user}|{internal_ip}`. Пользователи часто пробуют 1–2 раза подряд и звонят админам — 60 с слишком долго.
**Хост:** правило **универсальное** для любого хоста с RDG-событиями в домене (любой DC / gateway / session host, где стоит агент).
**Маршрутизация команды qwinsta:** команда уходит на хост, где **установлен агент** и куда привязано событие (`host_id`). Если session host ≠ gateway, в перспективе — таблица «gateway → session hosts» или агент на session host; MVP — агент на том же сервере, что шлёт события RDG.
### 3.3. UI — веб и Seaca
Бейдж **RDG flap** и кнопка **qwinsta** на событиях **302 и 303** (пара вычисляется при чтении):
- Веб: **Обзор**, **События**, карточка события
- **Seaca:** список событий + карточка → `POST .../actions/qwinsta` / `logoff`
**Поток:**
1. Оператор нажимает **qwinsta**.
2. `POST /api/v1/events/{id}/actions/qwinsta` → SAC выполняет **WinRM qwinsta** на **клиентский ПК** (`internal_ip` из события).
3. Результат — модалка: SESSION, USER, ID, STATE.
4. **logoff:** `POST .../actions/logoff { session_id }` → WinRM `logoff` → повторный qwinsta.
5. Нужен **Windows domain admin** в настройках SAC (`SAC_WIN_ADMIN_*` или UI).
```mermaid
sequenceDiagram
participant Op as Оператор
participant UI as SAC UI
participant API as SAC API
participant Ag as RDP-agent
Op->>UI: qwinsta
UI->>API: POST events/{id}/actions/qwinsta
API->>Ag: poll command qwinsta
Ag->>Ag: runas domain admin
Ag->>API: agent.command.result
API->>UI: modal + parsed sessions
Op->>UI: logoff session_id
UI->>API: POST actions/logoff
Ag->>API: agent.command.result
```
**Автоматический logoff без участия оператора — не делаем** в первых итерациях.
---
## 4. П.2 — Обновления агентов (Windows + Linux)
### 4.1. Настройки SAC → «Обновления агентов»
| Режим | Описание |
|-------|----------|
| **A — GPO / ручной** | Как сейчас: NETLOGON, `Deploy-LoginMonitor.ps1`, `update_ssh_monitor.sh`. SAC только показывает устаревшие версии. |
| **B — SAC** | SAC инициирует обновление; агент тянет пакет сам (pull). |
**Рекомендуемые версии** по продукту (`RDP-login-monitor`, `ssh-monitor`) — min / recommended; сверка с `host.product_version` (уже есть в UI «Хосты»).
**Источник пакетов** (выбор в настройках):
| Источник | Windows | Linux |
|----------|---------|-------|
| SMB / NETLOGON | `\\dc\NETLOGON\...` | — |
| Git | private repo kalinamall | `git pull` в `/opt/ssh-monitor` |
| HTTP(S) SAC | zip + checksum с SAC | zip + checksum |
### 4.2. B1 — Self-update после «пинка» (основной путь)
1. Admin: «Запросить обновление» на хосте / группе.
2. SAC: `host.pending_update = true` в ответе poll.
3. **Windows:** агент запускает `Deploy-LoginMonitor.ps1` (существующий скрипт).
4. **Linux:** агент запускает `update_ssh_monitor.sh`.
5. Ingest: `agent.update.started``agent.update.success` | `agent.update.failed`.
### 4.3. B2 — Fallback (SSH / WinRM)
Кнопка на карточке хоста: **«Обновить через WinRM»** / **«Fallback SSH»** (`POST .../actions/agent-update-fallback`). Лог операции — модалка сразу при старте (не ждёт конца).
Если за **N минут** нет `agent.update.*` или статус `failed` — тот же fallback может сработать автоматически (если включён в настройках).
| OS | Метод | Credentials | Как работает (SAC 0.20.18+) |
|----|--------|-------------|------------------------------|
| Linux | SSH | ключ после bootstrap (§5) | SAC передаёт `REPO_URL` / `GIT_BRANCH` из **Настройки → Обновления агентов** в `update_ssh_monitor.sh` (не дефолт GitHub на хосте) |
| Windows | WinRM | encrypted login/password в БД | 1) SAC `git fetch` RDP-login-monitor на сервере → zip с UTF-8 BOM для `.ps1`<br>2) Клиент по WinRM: `Invoke-WebRequest``https://<SAC_PUBLIC_URL>/api/v1/agent/rdp-bundle/<token>`<br>3) Распаковка в `C:\ProgramData\RDP-login-monitor\_sac_staging`<br>4) `Deploy-LoginMonitor.ps1 -SourceShareRoot` (файлы в `ProgramData\RDP-login-monitor\`) |
**Windows — требования:** domain admin в **Настройки → Управление хостами → Windows**; `rdp_git_repo_url` в **Обновления агентов**; ПК должен открывать `SAC_PUBLIC_URL` (HTTPS). **Git на клиенте не нужен.** GPO/NETLOGON по-прежнему основной путь в режиме GPO.
**nginx:** для `agent-update-fallback` и SSH/WinRM-тестов — `proxy_read_timeout 960s` (см. `deploy/nginx/sac.conf.tls.example`).
---
## 5. П.2C — Доступ SAC к хостам
### 5.1. Windows — WinRM
- **Один доменный admin** в **Настройки → Управление хостами → Windows**: `DOMAIN\user` + password (encrypted).
- Override на карточке хоста — опционально позже.
- Используется для fallback-обновления и (при необходимости) remote ops; для qwinsta/logoff MVP — **через агента** с теми же creds, переданными в command poll (TLS + API key, не пишутся на диск агента).
### 5.2. Linux — bootstrap password → SSH key → удалить password
На карточке хоста → «Доступ для управления»:
```
1. Admin вводит login + password (временно)
2. SAC по SSH: генерирует ed25519, добавляет pubkey в authorized_keys
3. SAC проверяет вход по ключу
4. Успех → password DELETE из БД, status = key_ready
5. Ошибка → password остаётся, status = bootstrap_failed, алерт
6. Кнопка «Переустановить ключи» — повтор bootstrap
```
Private key — encrypted в PostgreSQL.
### 5.3. Статусы доступа
| Статус | Linux | Windows |
|--------|-------|---------|
| `no_access` | нет данных | нет WinRM |
| `bootstrap_pending` | идёт настройка ключа | — |
| `key_ready` | SSH по ключу | — |
| `winrm_ready` | — | WinRM настроен |
---
## 6. П.3 — Настройки агента в карточке хоста
**Desired state** в SAC; агент забирает на poll:
```json
{
"config_revision": 42,
"settings": {
"ServerDisplayName": "UNMS Kalina",
"EnableRcmShadowControlMonitoring": true,
"GetInventory": true,
"HEARTBEAT_INTERVAL": "300"
}
}
```
- Whitelist ключей — без секретов (`SAC_API_KEY` только локально).
- **Windows:** поля из `login_monitor.settings.ps1`.
- **Linux:** поля из `/etc/ssh-monitor.conf`.
- Merge: remote перекрывает локальное, revision монотонно растёт.
---
## 7. API (черновик)
### 7.1. Poll (агент)
```http
GET /api/v1/agent/commands?since=<last_ack>
Authorization: Bearer sac_xxx
```
```json
{
"config_revision": 42,
"config": { "settings": { } },
"update": { "requested": false, "target_version": "2.0.38-SAC", "source": "smb://..." },
"commands": [
{ "id": "uuid", "type": "qwinsta", "params": { "user": "B26\\user" } }
]
}
```
### 7.2. UI actions (JWT: admin / monitor / mobile)
| Метод | Путь | Назначение |
|-------|------|------------|
| POST | `/api/v1/events/{id}/actions/qwinsta` | qwinsta (WinRM на клиентский ПК) |
| POST | `/api/v1/events/{id}/actions/logoff` | logoff `{ "session_id": 5 }` |
| GET | `/api/v1/events/{id}/actions/{cmd_id}` | Статус / результат |
| PATCH | `/api/v1/hosts/{id}/config` | Desired config |
| PATCH | `/api/v1/hosts/{id}/access` | Bootstrap / WinRM creds |
| GET/PATCH | `/api/v1/settings/agent-updates` | Режим A/B, версии, источники |
| GET/PATCH | `/api/v1/settings/host-management` | Доменный admin Windows |
### 7.3. Ingest (новые типы)
| `type` | Назначение |
|--------|------------|
| `agent.command.result` | stdout/stderr qwinsta, logoff |
| `agent.update.started` | начало self-update |
| `agent.update.success` | успех |
| `agent.update.failed` | ошибка |
---
## 8. Фазы реализации
| Фаза | Содержание | Статус |
|------|------------|--------|
| **1** | `rule:rdg_session_flap`, Problem, notify, флаг `rdg_flap` | ✅ SAC |
| **2** | **qwinsta** / **logoff** — веб + Seaca | ✅ |
| **3** | Poll API, `agent_commands` (агент) | ✅ RDP ≥ 2.1.0-SAC |
| **4** | WinRM qwinsta с сервера SAC | ✅ |
| **5** | Settings «Обновления агентов», SSH bootstrap | частично ✅ |
| **6** | Self-update B1 | ⏳ |
| **7** | SSH bootstrap + WinRM fallback | частично ✅ (SSH/WinRM update из UI, git URL из настроек) |
| **8** | Desired config per host | ⏳ |
**Миграция:** `016_agent_commands`. Env: `SAC_RDG_FLAP_*`, `SAC_WIN_ADMIN_USER`, `SAC_WIN_ADMIN_PASSWORD`.
---
## 9. Безопасность
- Команды qwinsta/logoff — JWT (веб и Seaca); `requested_by` в `agent_commands`.
- Rate limit на actions per host.
- logoff — confirm dialog; по умолчанию только matching user.
- Password bootstrap Linux — удаляется после успеха; re-bootstrap явной кнопкой.
- WinRM password — только encrypted storage, не в логах и не в Telegram.
---
## 10. Git / remotes (справка)
| Репозиторий | Prod (kalinamall) | Public (github) |
|-------------|-------------------|-----------------|
| security-alert-center | основной | sanitized |
| RDP-login-monitor | prod paths/secrets | sanitized settings |
| ssh-monitor | mirror-скрипты + URLs | без mirror-скриптов |
Mirror-скрипты: временно переписывают URL в docs под целевой host, пушат на remote, **откатывают** локальный main — чтобы в одной ветке не смешивать kalinamall/github URLs.
---
## См. также
- [agent-integration.md](agent-integration.md) — ingest, типы RDG
- [agent-update-backlog.md](agent-update-backlog.md) — ранний backlog (superseded деталями этого документа)
- [roadmap.md](roadmap.md) — v0.7