Skip to content

WebShell

WebShell — это терминал в браузере, который дотягивается до каждого зарегистрированного edge через тот же туннель geminio, который использует остальная платформа. Нет отдельного SSH bastion, нет jumpbox, нет входящего порта. Edge продолжает дозваниваться наружу; manager открывает мультиплексированный класс стрима для shell I/O.

Use case:

  • Агент предлагает фикс; вы кликаете «Open shell on edge-prod-04» и подтверждаете изменение, не покидая SPA.
  • Вендор / контрактор нуждается в разовом взгляде на один хост без VPN- enrolment.
  • Incident-response: каждая команда записывается со audit-строкой исходной сессии.

Архитектура

text
browser ──WebSocket──> manager:/v1/webshell/ws

                          ├─ Router.Register(sessionID, sink, ActiveSession)

                          └─ geminio Stream (shell class)


                              edge agent
                                  └─ pty.Start("/bin/bash")

Manager-side router — в internal/manager/biz/webshell/router.go. Он поддерживает справочник sessionID → Sink: WebSocket-handler регистрируется на connect, tunnel-incoming dispatcher маршрутизирует output / exit-пуши edge на правильный браузер.

go
// internal/manager/biz/webshell/router.go:57
type Router struct {
    mu          sync.RWMutex
    sinks       map[string]Sink
    meta        map[string]*ActiveSession // sessionID → metadata
    stdoutBytes sync.Map                  // sessionID → *uint64
}

HTTP / WebSocket handler живёт по соседству в internal/manager/server/webshell, так что router остаётся HTTP-agnostic и unit-testable.

Два класса стримов

Туннель geminio мультиплексирует:

  1. Control class — JSON RPC (выполнение скиллов, plugin signalling, пробы alert evaluator).
  2. Shell class — сырые байтовые стримы (один на WebShell-сессию, один на tail -f follower и т.д.).

Разделение на уровне туннеля важно, потому что shell I/O — burst-овый и unframed; смешивание его с control RPC обделяет последние. Каждый класс имеет свой бюджет backpressure.

Метаданные сессии

У каждой живой сессии есть ActiveSession:

go
// router.go:37
type ActiveSession struct {
    SessionID    string
    OngridUserID uint64
    SSHUser      string
    DeviceID     uint64
    EdgeID       uint64
    StartedAt    time.Time
    LastInputAt  time.Time // updated on every browser → edge frame
}

LastInputAt тикает на каждом нажатии клавиши (Router.TouchInput). Watchdog idle-timeout выселяет сессии старше сконфигурированного предела без недавнего ввода — защищается от утечки «я закрыл вкладку браузера с запущенной командой».

Audit recording

Два слоя:

  1. Header row — таблица webshell_sessions: кто, когда, какой edge, exit code, total bytes in/out.
  2. Stream recording — manager-side интерфейс Recorder берёт каждый байт, пересекающий провод (оба направления, с timestamp), и аппендит в asciinema-совместимый cast-файл под /var/lib/ongrid/webshell-recordings/<session_id>.cast. Админ страница /admin/webshell проигрывает их.

Интерфейс Recorder нарочно узкий — в production используется file sink; тесты инъектят fake; будущие cloud-blob бэкенды подключаются без касания остального стека.

Лимиты concurrency

Per-user cap: Router.CountByUser вызывается из WebSocket-open handler; over-cap соединения отвергаются HTTP 429. По умолчанию cap 5 (настраивается). Per-edge cap защищает от runaway-агента, открывающего 100 одновременных shell'ов.

Убийство сессий

Три пути убивают сессию:

  1. Browser close — disconnect WebSocket распространяется на edge, который kill -HUP-ит pty.
  2. Admin kill — админ-SPA вызывает Killer.Kill(reason="admin terminated") на Sink, который туннелирует close вниз на edge. Причина записывается в exit-строку сессии.
  3. Idle eviction — watchdog запускает Kill("idle timeout") на сессиях, чей LastInputAt превысил cap.
go
// router.go:50
type Killer interface {
    Kill(reason string)
}

Manager-side handler устанавливает Killer, когда регистрирует Sink. Любой Sink, который opt-in, становится admin-killable; остальные только browser-close-killable.

Role gating

WebShell гейтится на роли admin (ADR-022 RBAC). Роль user может чатиться с агентом, но не может открывать shell'ы; viewer может читать записи прошлых сессий, но не может открывать новые. Гейт работает на входе HTTP-handler, до WebSocket upgrade.

См. также

  • Skillsbash скилл — one-shot эквивалент WebShell (single command, без pty). Та же audit-подложка.
  • Установка edge — поднимание edge-агента хоста, чтобы WebShell мог до него дотянуться.
  • Архитектура — где сидит туннель geminio.