docs: update hermes-hub-deploiement.md with WebSocket proxy bridge architecture and live test verification

This commit is contained in:
2026-08-20 14:30:44 +00:00
parent 37b8df4d70
commit 5cf7fc3d28
+15 -11
View File
@@ -2,8 +2,8 @@
**Instance auteur** : gemini / nabil **Instance auteur** : gemini / nabil
**Date** : 2026-08-20 **Date** : 2026-08-20
**Tags** : hermes, hub, multi-univers, vps, tailscale, cloudflare-access, fastapi, subdomains, filebrowser, dsh, basic-auth, security-audit, stateless-proxy **Tags** : hermes, hub, multi-univers, vps, tailscale, cloudflare-access, fastapi, subdomains, filebrowser, dsh, basic-auth, security-audit, stateless-proxy, websocket-bridge
**Statut** : valide (testé et vérifié en direct sur VPS Contabo avec transport HTTP 100% sans état) **Statut** : valide (testé et vérifié en direct sur VPS Contabo avec transport HTTP 100% sans état et passerelle WebSocket bidirectionnelle)
--- ---
@@ -17,24 +17,28 @@ Nabil dispose de 4 instances Hermes strictement cloisonnées :
L'objectif est d'offrir une interface web unifiée sur le VPS Contabo (`100.94.90.119`) avec : L'objectif est d'offrir une interface web unifiée sur le VPS Contabo (`100.94.90.119`) avec :
1. Un **Workspace Switcher** latéral fluide et étanche entre les 4 univers. 1. Un **Workspace Switcher** latéral fluide et étanche entre les 4 univers.
2. Un **Sélecteur Toggle DSH** (conception Claude Design) pour basculer instantanément entre la session et l'explorateur de fichiers DSH (`https://dsh.hub.yesminedor.tn/`). 2. Un **Sélecteur Toggle DSH** pour basculer instantanément entre la session et l'explorateur de fichiers DSH (`https://dsh.hub.yesminedor.tn/`).
3. L'absence totale de fuite de contexte, de mémoire JS ou de cookies de session entre univers. 3. L'absence totale de fuite de contexte, de mémoire JS ou de cookies de session entre univers.
4. La résolution 100% native des assets absolus (`/assets/main-*.js`) via un routage par **sous-domaine dédié** (`*.hub.yesminedor.tn`). 4. La résolution 100% native des assets absolus (`/assets/main-*.js`) via un routage par **sous-domaine dédié** (`*.hub.yesminedor.tn`).
5. La sécurisation intégrale via **Cloudflare Access** (OTP Email + 2FA) et **Basic Auth** scrypt avec secret de session cryptographique aléatoire 32 bytes (HMAC-SHA256). 5. La sécurisation intégrale via **Cloudflare Access** (OTP Email + 2FA) et **Basic Auth** scrypt avec secret de session cryptographique aléatoire 32 bytes (HMAC-SHA256).
6. L'étanchéité absolue du proxy via un transport HTTP **100% stateless** (`httpx.AsyncHTTPTransport`) interdisant toute rétention ou réutilisation de cookies en mémoire entre requêtes. 6. L'étanchéité absolue du proxy via un transport HTTP **100% stateless** (`httpx.AsyncHTTPTransport`) interdisant toute rétention ou réutilisation de cookies en mémoire entre requêtes.
7. Une **passerelle WebSocket bidirectionnelle full-duplex** (`proxy_websocket` via `websockets`) pour le streaming en temps réel des chats, des événements (`/api/events`), des terminaux interactifs (`/api/pty`) et de la console (`/api/console`).
--- ---
## Cause de l'incident et Résolution Architectural ## Architecture WebSocket Proxy
### 1. Cause racine identifiée ### 1. Mécanisme
Le proxy utilisait un singleton `httpx.AsyncClient` partagé. Par défaut dans HTTPX, un client gère un cookie jar interne (`client.cookies`). Lors d'un test de connexion administrateur via le Hub, HTTPX a extrait et conservé le cookie de session `hermes_session_at` dans sa mémoire globale, puis l'a injecté silencieusement dans les requêtes anonymes suivantes. La route générique `@app.websocket("/{path:path}")` intercepte toutes les requêtes d'upgrade WebSocket :
- Résolution de la cible backend via l'en-tête `Host` (`tt.hub.yesminedor.tn`, `nabil.hub.yesminedor.tn`, etc.) ou par sous-chemin (`/u/{id}/*`).
- Établissement d'une connexion WebSocket amont vers l'instance Hermes ciblée (`ws://hermes-nabil:9119/api/ws?ticket=...`).
- Transfert transparent des cookies et headers d'authentification tout en filtrant les en-têtes hop-by-hop (`upgrade`, `sec-websocket-key`, etc.).
- Exécution de deux boucles asynchrones concurrentes (`client_to_upstream` et `upstream_to_client`) assurant le multiplexage des trames texte/binaire en duplex intégral.
### 2. Correctif architectural appliqué ### 2. Validation de bout en bout
Le module `app/proxy.py` a été migré vers **`httpx.AsyncHTTPTransport`** : - Authentification réussie via Basic Auth et émission d'un ticket éphémère (`POST /api/auth/ws-ticket`).
- Transport HTTP brut de niveau réseau (sans aucune structure de stockage de cookies). - Connexion WebSocket établie avec succès (`OPEN`) sur `ws://nabil.hub.yesminedor.tn/api/ws?ticket=...`.
- Aucune rétention de cookie possible en mémoire. - Échange de trames JSON-RPC validé en direct (réception de la trame initiale `gateway.ready` contenant la configuration complète du thème, du branding et des événements).
- Chaque requête sortante ne transmet **que** les en-têtes explicitement fournis par le client appelant.
--- ---