WebShell
WebShell ist ein browserorientiertes Terminal, das jede registrierte Edge durch denselben geminio-Tunnel erreicht, den der Rest der Plattform verwendet. Es gibt keinen separaten SSH-Bastion, keine Jumpbox, keinen eingehenden Port. Die Edge wählt weiter nach außen; der Manager öffnet eine multiplexte Stream-Klasse für Shell-I/O.
Use-Cases:
- Der Agent schlägt einen Fix vor; Sie klicken „Open shell on edge-prod-04" und bestätigen die Änderung, ohne die SPA zu verlassen.
- Anbieter / Auftragnehmer braucht einen einmaligen Blick auf einen Host ohne VPN-Enrollment.
- Incident-Response: jeder Befehl wird mit der Audit-Zeile der ursprünglichen Session aufgezeichnet.
Architektur
browser ──WebSocket──> manager:/v1/webshell/ws
│
├─ Router.Register(sessionID, sink, ActiveSession)
│
└─ geminio Stream (shell class)
│
▼
edge agent
└─ pty.Start("/bin/bash")Der manager-seitige Router ist in internal/manager/biz/webshell/router.go. Er hält ein sessionID → Sink-Verzeichnis: WebSocket-Handler registrieren beim Connect, der Tunnel-Incoming-Dispatcher routet die Output- / Exit-Pushes der Edge an den richtigen Browser.
// 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
}Der HTTP- / WebSocket-Handler liegt nebenan in internal/manager/server/webshell, sodass der Router HTTP-agnostisch und unit-testbar bleibt.
Die zwei Stream-Klassen
Der geminio-Tunnel multiplext:
- Control-Klasse — JSON-RPCs (Skill-Ausführung, Plugin-Signaling, Alert-Evaluator-Proben).
- Shell-Klasse — Roh-Byte-Streams (einer pro WebShell-Session, einer pro
tail -f-Follower, etc.).
Auf Tunnel-Ebene aufzuteilen zählt, weil Shell-I/O burstig und ungeframet ist; sie mit den Steuer-RPCs zu mischen verhungerte letztere. Jede Klasse hat ihr eigenes Backpressure-Budget.
Session-Metadaten
Jede Live-Session hat eine ActiveSession:
// 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 tickt bei jedem Tastendruck (Router.TouchInput). Ein Idle-Timeout-Watchdog vertreibt Sessions, die älter als das konfigurierte Limit ohne kürzlichen Input sind — verteidigt gegen den „Ich habe den Browser-Tab mit einem laufenden Befehl geschlossen"-Leak.
Audit-Aufzeichnung
Zwei Schichten:
- Header-Zeile —
webshell_sessions-Tabelle: wer, wann, welche Edge, Exit-Code, gesamte Bytes ein/aus. - Stream-Aufzeichnung — das manager-seitige
Recorder-Interface nimmt jedes Byte, das über die Leitung geht (beide Richtungen, zeitgestempelt) und hängt es an eine asciinema-kompatible Cast-Datei unter/var/lib/ongrid/webshell-recordings/<session_id>.castan. Die Admin-/admin/webshell-Seite spielt sie zurück.
Das Recorder-Interface ist absichtlich schmal — Produktion verwendet einen File-Sink; Tests injizieren ein Fake; zukünftige Cloud-Blob-Backends fallen ein, ohne den Rest des Stacks zu berühren.
Concurrency-Limits
Per-User-Cap: Router.CountByUser wird vom WebSocket-Open-Handler aufgerufen; Over-Cap-Verbindungen werden mit HTTP 429 abgelehnt. Default-Cap ist 5 (konfigurierbar). Per-Edge-Cap verteidigt gegen einen außer Kontrolle geratenen Agenten, der 100 gleichzeitige Shells öffnet.
Sessions killen
Drei Pfade killen eine Session:
- Browser-Close — WebSocket-Disconnect propagiert zur Edge, die das pty
kill -HUPt. - Admin-Kill — die Admin-SPA ruft
Killer.Kill(reason="admin terminated")auf demSinkauf, was einen Close zur Edge tunnelt. Der Grund wird in der Exit-Zeile der Session aufgezeichnet. - Idle-Eviction — der Watchdog feuert
Kill("idle timeout")auf Sessions, derenLastInputAtden Cap überschritten hat.
// router.go:50
type Killer interface {
Kill(reason string)
}Der manager-seitige Handler installiert einen Killer, wenn er den Sink registriert. Jeder Sink, der opt-in geht, wird admin-kill-bar; die anderen sind nur browser-close-killbar.
Rollen-Gating
WebShell ist auf die admin-Rolle gegatet (ADR-022 RBAC). Die user-Rolle kann mit dem Agenten chatten, kann aber keine Shells öffnen; viewer kann Aufzeichnungen vergangener Sessions lesen, kann aber keine neuen öffnen. Das Gate läuft am HTTP-Handler-Eintrag, vor dem WebSocket-Upgrade.
Siehe auch
- Skills — das
bash-Skill ist das One-Shot-Äquivalent von WebShell (einzelner Befehl, kein pty). Gleiches Audit-Substrat. - Edge-Installation — den Edge-Agenten eines Hosts hochfahren, damit WebShell ihn erreichen kann.
- Architektur — wo der geminio-Tunnel sitzt.