Files
security-alert-center/docs/agent-control-plane.md
T
PapaTramp 2aec488226 feat: RDG session flap rule, qwinsta/logoff queue and dashboard UI (0.9.13)
Detect 302→303 within 1–10s, Problem with 30s dedup, rdg_flap flag.
Agent command poll API, admin qwinsta/logoff from Overview. Migration 016.
2026-06-19 23:34:01 +10:00

327 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.
# 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 — «Обзор» → «Последние события»
В таблице [DashboardView](../frontend/src/views/DashboardView.vue) для строки события с `rdg_flap = true` (обычно **303**, второе в паре):
| … | Title | **Действия** |
|---|-------|--------------|
| … | RD Gateway event 303 | **[qwinsta]** |
**Поток:**
1. Оператор нажимает **qwinsta**.
2. `POST /api/v1/events/{id}/actions/qwinsta` → SAC ставит команду в очередь хоста.
3. Агент на poll выполняет `qwinsta` под **доменным admin** (см. §5).
4. Результат → модальное окно: таблица SESSIONNAME, USERNAME, ID, STATE.
5. **logoff:**
- по умолчанию — кнопка только у строк с **matching user** из события;
- дополнительно — возможность **выбрать любую строку** (на серверах бывает нестандартный вывод qwinsta).
6. `POST .../actions/logoff { session_id }` → подтверждение → команда агенту → `logoff {id} /v`.
7. Результат в модалке; Problem можно auto-resolve или оставить оператору.
```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)
Если за **N минут** нет `agent.update.*` или статус `failed`:
| OS | Метод | Credentials |
|----|--------|-------------|
| Linux | SSH из SAC | ключ после bootstrap (§5) |
| Windows | WinRM | encrypted login/password в БД |
---
## 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)
| Метод | Путь | Назначение |
|-------|------|------------|
| POST | `/api/v1/events/{id}/actions/qwinsta` | Запрос qwinsta |
| 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** на «Обзоре» | ✅ SAC UI |
| **3** | Poll API, `agent_commands`, env admin | ✅ SAC; ⏳ RDP-login-monitor |
| **4** | logoff в модалке | ✅ SAC UI |
| **5** | Settings «Обновления агентов» | ⏳ |
| **6** | Self-update B1 | ⏳ |
| **7** | SSH bootstrap + WinRM fallback | ⏳ |
| **8** | Desired config per host | ⏳ |
**Миграция:** `016_agent_commands`. Env: `SAC_RDG_FLAP_*`, `SAC_WIN_ADMIN_USER`, `SAC_WIN_ADMIN_PASSWORD`.
---
## 9. Безопасность
- Команды qwinsta/logoff — только **JWT admin**; audit log.
- 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