WebShell
WebShell est un terminal présenté au navigateur qui atteint chaque edge enregistré via le même tunnel geminio que le reste de la plateforme utilise. Il n'y a pas de bastion SSH séparé, pas de jumpbox, pas de port entrant. L'edge continue à sortir ; le manager ouvre une classe de stream multiplexée pour l'I/O shell.
Cas d'usage :
- L'agent suggère un fix ; vous cliquez « Open shell on edge-prod-04 » et confirmez le changement sans quitter la SPA.
- Un vendor / contractant a besoin d'un coup d'œil ponctuel sur un host sans enrolment VPN.
- Réponse à incident : chaque commande est enregistrée avec la ligne d'audit de la session d'origine.
Architecture
browser ──WebSocket──> manager:/v1/webshell/ws
│
├─ Router.Register(sessionID, sink, ActiveSession)
│
└─ geminio Stream (shell class)
│
▼
edge agent
└─ pty.Start("/bin/bash")Le router côté manager est dans internal/manager/biz/webshell/router.go. Il maintient un dictionnaire sessionID → Sink : les handlers WebSocket s'enregistrent à la connexion, le dispatcher tunnel-incoming route la sortie / les pushs d'exit de l'edge vers le bon navigateur.
// 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
}Le handler HTTP / WebSocket vit à côté dans internal/manager/server/webshell pour que le router reste HTTP-agnostique et testable unitairement.
Les deux classes de stream
Le tunnel geminio multiplexe :
- Classe de contrôle — RPCs JSON (exécution de skill, signalling de plugin, sondes d'evaluator d'alerte).
- Classe de shell — streams d'octets bruts (un par session WebShell, un par follower
tail -f, etc.).
Le découpage au niveau du tunnel importe parce que l'I/O shell est en burst et non framée ; la mélanger avec les RPCs de contrôle prive les seconds. Chaque classe a son propre budget de backpressure.
Métadonnées de session
Chaque session vivante a une 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 tique à chaque keystroke (Router.TouchInput). Un watchdog de timeout d'idle évince les sessions plus anciennes que la limite configurée sans entrée récente — défend contre la fuite « j'ai fermé l'onglet du navigateur avec une commande qui tourne ».
Enregistrement d'audit
Deux couches :
- Ligne d'en-tête — table
webshell_sessions: qui, quand, quel edge, code de sortie, total d'octets in/out. - Enregistrement de stream — l'interface
Recordercôté manager prend chaque octet qui traverse le câble (les deux directions, horodaté) et l'ajoute à un fichier cast compatible asciinema sous/var/lib/ongrid/webshell-recordings/<session_id>.cast. La page admin/admin/webshellles rejoue.
L'interface Recorder est étroite exprès — la production utilise un sink fichier ; les tests injectent un fake ; les futurs backends cloud-blob drop in sans toucher au reste de la stack.
Limites de concurrence
Plafond par utilisateur : Router.CountByUser est appelé depuis le handler d'ouverture WebSocket ; les connexions au-dessus du plafond sont rejetées avec HTTP 429. Plafond par défaut 5 (configurable). Le plafond par edge défend contre un agent fou ouvrant 100 shells concurrents.
Tuer les sessions
Trois chemins tuent une session :
- Fermeture du navigateur — la déconnexion WebSocket propage à l'edge, qui
kill -HUPle pty. - Kill admin — la SPA admin appelle
Killer.Kill(reason="admin terminated")sur leSink, qui tunnel un close vers l'edge. La raison est enregistrée dans la ligne d'exit de la session. - Éviction d'idle — le watchdog déclenche
Kill("idle timeout")sur les sessions dont leLastInputAta dépassé le plafond.
// router.go:50
type Killer interface {
Kill(reason string)
}Le handler côté manager installe un Killer quand il enregistre le Sink. Tout Sink qui s'opt-in devient admin-killable ; le reste n'est browser-close-killable que.
Gating de rôle
WebShell est gaté sur le rôle admin (RBAC ADR-022). Le rôle user peut chatter avec l'agent mais ne peut pas ouvrir de shells ; le rôle viewer peut lire les enregistrements de sessions passées mais ne peut pas en ouvrir de nouvelles. Le gate tourne à l'entrée du handler HTTP, avant l'upgrade WebSocket.
Voir aussi
- Skills — le skill
bashest l'équivalent one-shot de WebShell (commande unique, pas de pty). Même substrat d'audit. - Installation edge — démarrer l'agent edge d'un host pour que WebShell puisse l'atteindre.
- Architecture — où se situe le tunnel geminio.