# Suivi – VPN on-demand studioE5 (client A) ## ✅ Ce qui fonctionne 1. **Agent standalone (mode console / systray)** - Exécutable : `agent/studioE5-agent` - Config lu depuis `/studioE5-config.json` - Mode console : `-no-tray` 2. **VPN on-demand dans l'agent** - L’agent ne démarre plus Tailscale au boot. - Le VPN se lance automatiquement à la création/démarrage d’une instance, ou sur commande serveur. - Implémentation basée sur les binaires `tailscaled` + `tailscale up` (pas `tsnet`, car `tsnet` ne loguait pas automatiquement avec une authkey sur un state vierge). 3. **Commandes serveur → agent** - Endpoint de test : `POST /api/internal/send-to-node` - Actions supportées : `start_vpn`, `stop_vpn`, `start`, `stop`, `reset`, `delete`. 4. **Resolver/serveur dans le tailnet studioe5** - Service `resolver-vpn` (conteneur Tailscale) partage le netns du `resolver`. - Le resolver peut joindre les IPs Tailscale des nodes (`ping 100.64.0.x` OK). 5. **Instance WordPress démarrée avec succès** - Le resolver a renvoyé une 302 WordPress via `http://resolver:2020/`. 6. **Activation zéro-config de l’agent (modèle commercialisable)** - L’agent démarre sans `headscale_url` ni `headscale_auth_key`. - L’utilisateur entre seulement un code d’activation. - Le serveur envoie la config Headscale, l’agent la sauvegarde et démarre le VPN automatiquement. ## ✅ Blocage levé **Rate limit Let’s Encrypt pour `edudeploy.com` est levé.** Le 2026-06-23 vers 09:35 UTC, Caddy a pu obtenir un certificat Let’s Encrypt pour `test-wp-001.studioe5.edudeploy.com` : ``` tls.obtain: certificate obtained successfully identifier=test-wp-001.studioe5.edudeploy.com issuer=acme-v02.api.letsencrypt.org-directory ``` Le flux complet HTTPS public est désormais validé : ```bash curl -sS -I -L https://test-wp-001.studioe5.edudeploy.com/ # => HTTP/2 302 -> HTTP/2 200 (WordPress install.php) ``` Le DNS wildcard `*.studioe5.edudeploy.com` est en place. Caddy utilise toujours `tls { on_demand }` et émet un certificat par sous-domaine d’instance. ## 🎯 Validation du flux HTTPS public Le 2026-06-23 09:39 UTC, le flux complet a été validé : ```text Client (HTTPS) → Caddy (:443) → resolver (:2020) → Tailnet (100.64.0.8) → agent → WordPress (:8001) ``` Résultat : ```bash $ curl -sS -I -L https://test-wp-001.studioe5.edudeploy.com/ HTTP/2 302 location: https://test-wp-001.studioe5.edudeploy.com/wp-admin/install.php ... HTTP/2 200 ``` - Certificat Let’s Encrypt obtenu automatiquement par Caddy (`tls { on_demand }`). - Le resolver réécrit les en-têtes `Location` et le contenu HTML pour passer de `http://` à `https://`. ## 📁 Fichiers modifiés (non exhaustif) - `agent/tailscale.go` – lancement `tailscaled` + `tailscale up`, gestion start/stop/status. - `agent/websocket.go` – handlers `start_vpn` / `stop_vpn`, `ensureTailscale()`. - `agent/docker.go` – remplacement des placeholders `{PORT}` et `{INSTANCE_ID}` dans les compose. - `docker-compose.yml` – ajout du sidecar `resolver-vpn`, suppression des `cap_add`/`ip route` obsolètes sur `server`/`resolver`. - `Caddyfile` – configuration on-demand TLS pour les instances. - `.env` – clé pré-auth Headscale mise à jour (clé réutilisable). ## 🧪 Tests / environnement de test actuel Agent de test lancé en arrière-plan : - data-dir : `/tmp/studioe5-test-clienta` - node-id : `vps-8fc665eb` - tailnet IP actuelle : `100.64.0.8` - PID : `3151830` (lancé le 2026-06-23 09:36 UTC) Instance de test créée : - ID : `test-wp-001` - Node : `vps-8fc665eb` - Port : `8001` - Template : `wordpress-wordpress-latest` - État : WordPress répond sur `http://127.0.0.1:8001` **et en HTTPS public sur `https://test-wp-001.studioe5.edudeploy.com/`**. ## 🪟 Fix agent Windows v0.3.1 Problème rencontré sur le PC de test (`OMEGA-GAMER-dc166b1a`) : - Le nœud apparaissait `online` dans le dashboard mais sans IP Tailscale. - `tailscale.exe ip -4` retournait une erreur de connexion au socket local. Cause racine : - L’agent lançait `tailscaled` avec `--socket=.sock`, mais **Tailscale sur Windows utilise des named pipes** (`\\.\pipe\...`), pas des sockets Unix. - De plus, les commandes `podman`/`docker`/`tailscale` ouvraient une fenêtre console à chaque exécution. Corrections apportées (`agent/tailscale.go`, `agent/docker.go`, `agent/instance.go`, `agent/systray.go`, `agent/ui.go`, `agent/main.go`) : - Sur Windows, utilisation de la named pipe `\\.\pipe\studioe5-tailscaled`. - Application de `hideWindow` à tous les processus enfants (Tailscale, Podman, Docker, ouverture navigateur, redémarrage agent). - Redirection des logs agent vers `/agent.log` et des logs `tailscaled` vers `/tailscale/tailscaled.log`. - Suppression de `--operator=root` sur Windows (non pertinent). - Ajout de `--unattended` au `tailscale up` sur Windows pour que le daemon reste connecté après la déconnexion du client CLI. - Correction du chemin `dataDir` passé à `startTailscale` (évitait un double dossier `tailscale/tailscale`). Validation manuelle sur Windows : ```powershell .\tailscaled.exe --state="C:\...\data\tailscale.state" --socket="\\.\pipe\studioe5-tailscaled" --tun=userspace-networking .\tailscale.exe --socket="\\.\pipe\studioe5-tailscaled" status # => Logged out (NeedsLogin) ``` ## 🪟 Agent v0.3.5 – forwarding entrant Windows + UI locale + cycle de vie ### Problème Sur Windows, Tailscale en `userspace-networking` ne forwarde pas automatiquement les connexions entrantes du Tailnet vers `localhost`. Résultat : les URLs publiques retournaient une erreur 502/timeout, bien que l’agent soit `online`. Logs caractéristiques : ```text client -> backend close connection: close tcp 100.64.0.12:8080->100.64.0.11:xxxxx: endpoint not connected ``` ### Solution : `tailscale serve` automatique L’agent configure automatiquement un proxy TCP pour chaque instance démarrée : ```powershell tailscale serve --bg --tcp= tcp://localhost: ``` | Action agent | Commande Tailscale | |--------------|--------------------| | Démarrage d’instance | `serve --bg --tcp= tcp://localhost:` | | Arrêt d’instance | `serve --bg --tcp= off` | | Suppression d’instance | `serve --bg --tcp= off` | | Redémarrage de l’agent | reconfiguration pour les instances déjà `running` | Fichiers modifiés : `agent/tailscale.go`, `agent/websocket.go`, `agent/main.go`, `agent/ui.go`. ### UI locale modernisée - Tableau de bord avec indicateurs de service. - Liste des applications avec badges de statut. - Boutons d’action par instance : **Démarrer**, **Arrêter**, **Redémarrer**, **Supprimer**. - Panneau de logs et diagnostic intégré. - Panneau de configuration (URL serveur, Headscale, node ID). ### Cycle de vie des instances - **Arrêter** → `docker compose stop` (volumes conservés). - **Démarrer** → `docker compose start` (ou `up -d` la première fois). - **Redémarrer** → `docker compose down -v` + recréation (données remises à zéro). - **Supprimer** → `docker compose down -v` + suppression des fichiers. - À la fermeture de l’agent, les instances en cours sont arrêtées proprement (`stop`) et le serveur est notifié (`instance_stopped`). ### Démarrage du VPN après activation L’agent redémarre `tailscaled` automatiquement au lancement, même si la clé pré-auth a déjà été utilisée. Il se base sur l’état persistant `tailscaled.state` (`tailscale up` sans `--authkey`). ### Téléchargement - **Windows (archive)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5-windows.zip` - **Windows (exe)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5.exe` - **Linux** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5` ## 🛠️ Commandes utiles pour reprendre ### Voir l’agent de test ```bash pgrep -a studioe5-agent ``` ### Relancer l’agent de test (si besoin) ```bash mkdir -p /tmp/studioe5-test-clienta cat > /tmp/studioe5-test-clienta/studioE5-config.json < HTTP/2 302 -> HTTP/2 200 (WordPress install.php) ``` Le flux `UI → API → WebSocket → agent → Docker → VPN → Caddy → HTTPS public` est fonctionnel. ## 💻 Téléchargement de l’agent L’agent est servi par Caddy depuis le dossier `agent/` monté dans le conteneur Caddy (`./agent:/usr/share/caddy/agent`). ### Binaires disponibles - **Windows (archive complète)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5-windows.zip` - Contient `studioE5-agent.exe` + `tailscale-bin/windows/` (`tailscale.exe`, `tailscaled.exe`, `wintun.dll`) + `README-Windows.txt`. - **Windows (exécutable seul)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5.exe` - Nécessite d’avoir installé Tailscale Windows séparément ou d’avoir les binaires dans `tailscale-bin/windows/`. - **Linux** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5` ### Builder / préparer les binaires ```bash cd /opt/studioe5-client-a/agent # 1. Télécharger les binaires Tailscale Windows (nécessite msitools) ./download-tailscale-bins.sh 1.98.4 # 2. Builder l’agent pour Windows et Linux (macOS nécessite CGO) ./build.sh ``` Le `build.sh` génère automatiquement `studioE5-agent-v0.3.5-windows.zip` et copie les binaires versionnés dans `server/public/`. ### Flow d’activation zéro-config (modèle commercialisable) L’élève/employé n’a **aucune configuration technique** à saisir : 1. **Télécharger** l’agent Windows (`studioE5-agent-v0.3.4-windows.zip`). 2. **Extraire** et **lancer** `studioE5-agent.exe`. 3. **Entrer le code d’activation** à 6 caractères fourni par l’établissement (affiché dans l’UI locale `http://localhost:7070`). 4. L’agent contacte le serveur, le serveur vérifie le code et renvoie **automatiquement** : - l’identité de l’élève (`studentName`) - l’URL Headscale - la clé pré-auth Headscale 5. L’agent sauvegarde ces informations localement et **démarre automatiquement le VPN**. 6. L’agent est alors visible dans le dashboard et peut recevoir des instances. ### Configuration manuelle (mode debug / admin) Si besoin, on peut toujours forcer une config via `data/studioE5-config.json` : ```json { "server": "wss://studioe5.edudeploy.com/api/websocket", "headscale_url": "https://headscale.studioe5.edudeploy.com", "headscale_auth_key": "CLE_PREAUTH_ICI", "node_id": "IDENTIFIANT_DU_POSTE", "data_dir": "C:\\studioE5-agent\\data" } ``` > ⚠️ `headscale_auth_key` doit être une clé pré-auth réutilisable valide pour le tailnet studioe5. Ne jamais commiter cette clé. Lancement : ```powershell .\studioE5-agent.exe -no-tray -data-dir C:\studioE5-agent\data ``` ## 🔒 Durcissement du code d’activation ### Génération - Les codes sont générés avec `crypto.randomBytes` (au lieu de `Math.random`). - Longueur conservée à 6 caractères, alphabet sans ambiguïté (`ABCDEFGHJKLMNPQRSTUVWXYZ23456789`). - Un champ `activationCodeExpiresAt` a été ajouté au modèle `Student` ; les codes expirent après **60 minutes**. ### Rate-limiting - Maximum de **5 tentatives d’activation par code** sur une fenêtre de **15 minutes**. - Maximum de **5 tentatives par `nodeId`** sur la même fenêtre. - Au-delà, le serveur répond `activation_failed` avec `Too many attempts`. ### Cycle de vie - Le code est **invalide après une activation réussie** (`activationCode` et `activationCodeExpiresAt` mis à `null`). - Un code expiré renvoie `Code expired`. - Un code déjà utilisé renvoie `Invalid code`. ### Tests validés - Activation valide → `activated` + token node reçu. - Code expiré → `Code expired`. - Code déjà utilisé → `Invalid code`. - 5+ tentatives invalides → `Too many attempts`. ## 🔒 ACL Headscale (isolation du tailnet) ### Objectif Par défaut, tous les nœuds du tailnet peuvent communiquer entre eux. Les ACL restreignent la connectivité au strict nécessaire : - les agents élèves ne peuvent pas se parler entre eux ; - le resolver peut atteindre les agents sur leurs ports d’instance ; - les agents peuvent joindre le resolver sur son port HTTP interne. ### Mise en œuvre - Fichier de politique : `headscale/acl_policy.hujson`. - `headscale/config.yaml` pointe vers ce fichier via `policy.path`. - Le resolver a été déplacé dans un utilisateur Headscale dédié `resolver` (clé `HEADSCALE_RESOLVER_AUTH_KEY`). - Les agents utilisent l’utilisateur `studioe5` et sont tagués `tag:student-agent`. - Les nouveaux agents recevront automatiquement le tag via la nouvelle clé pré-auth `HEADSCALE_AUTH_KEY` (créée avec `--tags tag:student-agent`). ### Contenu de la politique ```json { "groups": { "group:agents": ["studioe5@studioe5.local"], "group:resolvers": ["resolver@studioe5.local"] }, "tagOwners": { "tag:student-agent": ["studioe5@studioe5.local"], "tag:resolver": ["resolver@studioe5.local"] }, "acls": [ { "action": "accept", "src": ["tag:resolver"], "dst": ["tag:student-agent:*"] }, { "action": "accept", "src": ["tag:student-agent"], "dst": ["tag:resolver:2020"] } ] } ``` ### Tests validés | Test | Résultat | |------|----------| | `resolver` ping agent | ✅ OK | | Agent → agent (port instance) | ❌ bloqué (timeout) | | Agent → resolver:2020 | ✅ OK | | Flux HTTPS public | ✅ HTTP 200 | ## 🔒 Authentification du canal serveur → agent ### Token d’authentification par nœud - Le modèle `Node` dispose d’un champ `token` unique. - L’agent envoie son token dans l’en-tête `Authorization: Bearer ` lors de la connexion WebSocket. - Le serveur rejette toute connexion/register dont le token ne correspond pas au `nodeId` (fermeture `1008`). - Lors de l’activation, le serveur génère un token aléatoire (32 octets hex) et le renvoie dans le message `activated` ; l’agent le sauvegarde dans `/node.token` (permissions `0600`). - Pour les nœuds existants sans token, le serveur en génère un à la première connexion et l’envoie via `set_token`. ### Endpoint `/api/internal/send-to-node` - Protégé par la variable d’environnement `INTERNAL_API_KEY`. - Requiert l’en-tête `Authorization: Bearer `. - Appel sans clé → `401 Unauthorized`. ### Routes API métier - Les routes de gestion des instances (`/api/instances`) requièrent une session NextAuth valide. - Un administrateur ne peut agir que sur les ressources de son établissement ; le `superadmin` peut tout voir/tout faire. ### Endpoint `/api/resolve` - Protégé par la même clé `INTERNAL_API_KEY`. - Requiert l’en-tête `Authorization: Bearer `. - Le resolver (`resolver:2020`) ne l’utilise pas ; il interroge directement PostgreSQL. Cette route est donc réservée aux outils/scripts internes authentifiés. ### Exemples de commandes avec la clé interne ```bash KEY=$(grep INTERNAL_API_KEY /opt/studioe5-client-a/.env | cut -d= -f2) curl -sS -X POST https://studioe5.edudeploy.com/api/internal/send-to-node \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $KEY" \ -d '{"nodeId":"vps-8fc665eb","message":{"action":"start_vpn"}}' curl -sS -H "Authorization: Bearer $KEY" \ "https://studioe5.edudeploy.com/api/resolve?subdomain=test-wp-001" ``` ## 🔒 Clés pré-auth Headscale éphémères ### Principe À l’activation zero-config, le serveur génère désormais une **clé pré-auth unique et à usage unique** pour chaque agent, au lieu d’envoyer la clé réutilisable `HEADSCALE_AUTH_KEY`. Avantages : - une clé compromise ne permet pas d’enregistrer d’autres nœuds ; - traçabilité directe entre une activation et une clé Headscale ; - expiration courte (15 min) ; - la clé n’est **pas persistée** dans `studioE5-config.json` côté agent. ### Implémentation | Composant | Changement | |-----------|------------| | `server/lib/headscale.ts` | Nouveau helper : `getHeadscaleUserId()` + `createEphemeralPreAuthKey()` appelant `POST /api/v1/preauthkey`. | | `server/lib/websocket.ts` | Sur `activate`, génère une clé éphémère taguée `tag:student-agent` pour l’utilisateur `studioe5`. Fallback sur `HEADSCALE_AUTH_KEY` si `HEADSCALE_API_KEY` n’est pas configurée. | | `agent/websocket.go` | La clé reçue est utilisée immédiatement mais **n’est plus écrite** dans `studioE5-config.json`. | | `agent/tailscale.go` | `tailscale up` fonctionne sans `--authkey` quand le state Tailscale existe déjà (reconnexion). | | `.env.example` / `docker-compose.yml` | Ajout de `HEADSCALE_API_KEY` pour le service `server`. | ### Configuration requise Générer une clé API Headscale (depuis le conteneur ou la CLI) : ```bash cd /opt/studioe5-client-a # Clé valable 10 ans (87600h) pour éviter un renouvellement fréquent. docker compose exec headscale headscale apikeys create -e 87600h ``` Puis l’ajouter dans `.env` : ```bash HEADSCALE_API_KEY=hskey-api-... ``` > ⚠️ La clé API Headscale a une expiration par défaut de 90 jours. La clé de production a été créée avec une expiration de **10 ans** (`-e 87600h`). Les anciennes clés ont été révoquées. ### Rotation / renouvellement Si la clé doit être changée : 1. Créer une nouvelle clé API : ```bash docker compose exec headscale headscale apikeys create -e 87600h ``` 2. Mettre à jour `.env` : ```bash HEADSCALE_API_KEY= ``` 3. Redémarrer le serveur : ```bash docker compose up -d server ``` 4. Révoquer l’ancienne clé : ```bash docker compose exec headscale headscale apikeys expire --id ``` ### Déploiement effectué - Clé API créée et ajoutée au `.env` de production. - Image serveur rebuildée et redémarrée. - Agents Linux/Windows rebuildés en v0.3.4 et copiés dans `server/public/`. ## 🔒 Sécurité — points restants à traiter > Le certificat wildcard `*.studioe5.edudeploy.com` est désormais du ressort du **deployeur** (voir `docs/ONBOARDING_CLIENT.md`). Les points ci-dessous concernent l’application studioE5 proprement dite. ### Gestion et rotation des secrets | Secret | Où ? | Action | |--------|------|--------| | `INTERNAL_API_KEY` | `.env` serveur | Prévoir une procédure de rotation régulière. | | `HEADSCALE_API_KEY` | `.env` serveur | Rotation tous les 10 ans max, stockage sécurisé. | | `NEXTAUTH_SECRET` | `.env` serveur | Génération robuste, rotation si suspicion de fuite. | | `DATABASE_URL` | `.env` serveur | Utilisateur DB dédié, mot de passe fort. | | `node.token` | `/node.token` | Vérifier permissions `0600` sur tous les OS. | ### Durcissement des conteneurs - Limiter les `cap_add` au strict minimum. - Faire tourner les services avec un utilisateur non-root quand possible. - Mettre à jour régulièrement les images de base (Caddy, Node, Postgres, Headscale). - Scanner les images Docker pour les CVE. ### Mises à jour de sécurité - Mise à jour des binaires Tailscale (Windows et Linux). - Mise à jour des images Docker (`server`, `resolver`, `caddy`, `postgres`, `headscale`). - Mise à jour de l’OS des VPS et des postes agents. - Mécanisme de mise à jour automatique ou notification de l’agent. ### Logs d’audit - Tracer la création / suppression d’instances. - Tracer la génération et l’usage des codes d’activation. - Tracer les actions admin (connexion, création d’élève, démarrage/arrêt VPN). - Conservation et consultation des logs d’audit. ### Backups et reprise d’activité - Backup régulier de la base PostgreSQL. - Backup du state Headscale. - Backup des states Tailscale côté agents. - Procédure de restauration documentée et testée. ### Sécurité du build et distribution de l’agent - Vérifier l’intégrité des binaires Tailscale téléchargés (checksum / signature). - Signer l’exécutable Windows `studioE5-agent.exe` pour éviter les alertes Defender. - Fournir un hash SHA256 des archives d’agent. ### RGPD et données personnelles - Justifier la conservation des noms/prénoms des élèves. - Gérer les droits d’accès, la suppression de compte et l’export de données. - Définir la durée de conservation des logs et historiques. ### Sécurité réseau complémentaire - Restreindre l’accès à `/api/internal/send-to-node` par IP source si possible. - Vérifier l’exposition publique du dashboard Headscale et la durcir si nécessaire. - Évaluer si `headscale.studioe5.edudeploy.com` doit rester public. ### Rate limiting et quotas - Rate-limiting global sur les routes publiques (`/api/auth/*`, activation, création d’instance). - Limitation du nombre d’instances par élève et par établissement. - Protection contre les abus sur la génération de codes d’activation. ### Tests de sécurité - Tests d’intrusion légers (agent → agent, accès aux endpoints internes sans clé, accès à une instance d’un autre élève). - Tests automatisés du flux complet avant chaque release. --- ## 📋 Prochaines étapes à faire ### ✅ Terminé - [x] Rate limit Let’s Encrypt levé. - [x] Flux HTTPS public validé (`test-wp-001.studioe5.edudeploy.com`). - [x] Branche `feat/studioe5-vpn-ondemand` créée, commit `124543d`. - [x] Flux complet UI → API → WebSocket → agent → Docker → VPN → Caddy validé. - [x] Packager les binaires Tailscale pour Windows (`studioE5-agent-v0.3.5-windows.zip`). - [x] **Sécurité – authentification du canal serveur → agent** (token par nœud, clé API interne, sessions NextAuth sur les routes API). - [x] **Sécurité – durcissement du code d’activation** (`crypto.randomBytes`, expiration 60 min, rate-limiting, invalidation après usage). - [x] **Sécurité – ACL Headscale** (isolation agent ↔ agent, resolver → agent autorisé). - [x] **Sécurité – clés pré-auth Headscale éphémères** (génération côté serveur via `HEADSCALE_API_KEY`, non persistées côté agent). - [x] **Agent v0.3.5 – forwarding entrant Windows** (`tailscale serve` automatique au démarrage de chaque instance). - [x] **Agent v0.3.5 – UI locale moderne** (dashboard, logs, progression, actions d’instance). - [x] **Agent v0.3.5 – cycle de vie des instances** (`stop`/`start` préservent les volumes, `reset`/`delete` effacent). - [x] **Agent v0.3.5 – cleanup au shutdown** (arrêt propre de Tailscale et des instances, notification serveur). - [x] **Synchronisation dashboard** (messages `instance_stopped` / `instance_deleted` traités côté serveur). ### ⏳ Reste à faire - [ ] **Certificat wildcard** : transféré au deployeur (`docs/ONBOARDING_CLIENT.md`). L’étude technique reste disponible ci-dessous pour référence. - [ ] **Nettoyer les instances/agent de test** une fois le push effectué. - [ ] **Nettoyer les anciens nodes/volumes Headscale** de test (nœuds `edubox`, `prof`, `invalid-*` hors ligne à supprimer). - [ ] **Pousser la branche** vers Gitea dès que le remote sera accessible. - [ ] **Documenter la procédure de mise en production** pour le client A (config agent, clés Headscale, ports, ACL, etc.). - [ ] **Étude – interface de déploiement multi-clients** : outil de provisionning d’un nouveau serveur client + agent générique (option A : URL serveur déterminée à l’activation). - [ ] **Sécurité – gestion et rotation des secrets** (`INTERNAL_API_KEY`, `HEADSCALE_API_KEY`, `NEXTAUTH_SECRET`, `DATABASE_URL`). - [ ] **Sécurité – durcissement des conteneurs** (`cap_add`, utilisateurs non-root, scans CVE). - [ ] **Sécurité – mises à jour de sécurité** (Tailscale, images Docker, OS agents). - [ ] **Sécurité – logs d’audit** (instances, codes d’activation, actions admin). - [ ] **Sécurité – backups et reprise d’activité** (DB, state Headscale, states agents). - [ ] **Sécurité – intégrité et signature de l’agent** (checksum Tailscale, signature Windows, hash SHA256). - [ ] **Sécurité – conformité RGPD** (données élèves, suppression de compte, export). - [ ] **Sécurité – restriction réseau** (endpoint interne, dashboard Headscale). - [ ] **Sécurité – rate limiting et quotas** (routes publiques, instances par élève/établissement). - [ ] **Sécurité – tests de sécurité** (intrusion légère, tests automatisés avant release). ## 💡 Améliorations UI ### ✅ Console / log intégrée dans l’agent (v0.3.5) Les logs de l’agent sont redirigés vers `/agent.log` et diffusés en temps réel dans l’UI locale (`http://localhost:7070`) via le WebSocket existant. ### ✅ Barre de progression (v0.3.5) L’agent envoie des messages `progress` au frontend pendant le démarrage d’une instance : | Étape | Poids | |-------|-------| | Préparation de l’application | 10 % | | Configuration de l’application | 30 % | | Application en cours de démarrage | 60 % | | Connexion sécurisée active | 80 % | | Finalisation de l’installation | 90 % | | Application prête | 100 % | ### Boutons d’action par instance (v0.3.5) L’UI locale affiche désormais des boutons **Démarrer**, **Arrêter**, **Redémarrer** et **Supprimer** pour chaque instance. ## 🚀 Scalabilité commerciale — déploiement multi-clients ### Objectif Permettre de déployer facilement une stack studioE5 complète pour un nouvel établissement/client sur un VPS dédié, sans intervention technique lourde. ### Architecture cible - **Un serveur = un client** : chaque établissement a sa propre stack Docker Compose, sa base PostgreSQL, son Headscale et son Caddy. - **Agent générique (option A)** : un seul binaire agent pour tous les clients. L’URL du serveur cible est déterminée au moment de l’activation, pas hardcodée dans l’agent. - Pistes : code d’activation résolu par un hub central, code structuré contenant l’identifiant du serveur, ou champ URL serveur saisi dans l’UI locale. - **Interface de déploiement** : dashboard superadmin (hub) permettant de créer un client, provisionner le VPS, générer les secrets et retourner les informations de connexion. ### Prérequis techniques à préparer Avant de pouvoir déployer un nouveau client en quelques clics, il faut encore préparer les éléments suivants : | # | Élément | État | Détail | |---|---------|------|--------| | 1 | **Agent générique** | ⏳ À faire | `defaultServerURL` est hardcodé (`wss://studioe5.edudeploy.com/api/websocket`). L’agent doit pouvoir déterminer l’URL serveur cible à l’activation (option A : champ URL, hub de résolution, ou code structuré). | | 2 | **Script de provisionning** | ⏳ À faire | Aucun outil automatisé pour provisionner un VPS vierge : installation Docker, déploiement de la stack, génération des secrets, création des clés Headscale, configuration DNS wildcard. | | 3 | **Registry d’images** | ⏳ À faire | Les images `server` et `resolver` sont buildées sur le serveur cible. Il faut un registry privé pour builder une fois et déployer partout. | | 4 | **Hub central** | ⏳ À faire | Dashboard superadmin listant les clients, état des serveurs, versions déployées, logs distants et mises à jour. | | 5 | **Mises à jour à distance** | ⏳ À faire | Mécanisme pour pousser une nouvelle version du serveur et de l’agent sur tous les déploiements clients. | | 6 | **Monitoring / support** | ⏳ À faire | Collecte centralisée de logs, alertes (serveur down, certificat expiré, agent hors ligne). | | 7 | **Branding / personnalisation** | ⏳ À faire | Logo, nom de l’établissement, couleurs configurables par client. | | 8 | **Tests automatisés** | ⏳ À faire | Tests du flux activation → VPN → instance → HTTPS public pour valider chaque nouveau déploiement. | | 9 | **Documentation procédure prod** | ⏳ À faire | Procédure complète de mise en production pour un nouveau client. | ### Statut - ⏳ À étudier et planifier plus tard. L’architecture actuelle (un serveur par client + agent zero-config) est déjà compatible avec cette vision, mais le code n’est pas encore industrialisé pour un déploiement à grande échelle. ## 🔒 Étude certificat wildcard `*.studioe5.edudeploy.com` ### Pourquoi passer en wildcard ? Avec `tls { on_demand }`, Caddy émet **un certificat Let’s Encrypt par sous-domaine d’instance**. Cela expose au rate limit de 50 certificats par domaine principal (`edudeploy.com`) sur 7 jours. Un certificat wildcard unique (`*.studioe5.edudeploy.com`) couvre tous les sous-domaines d’instances et évite ce problème. ### Contrainte technique Un certificat wildcard nécessite le **challenge DNS-01** (le challenge HTTP-01 ne permet pas de valider `*.domain.tld`). Caddy doit donc pouvoir créer un enregistrement TXT automatiquement chez le registrar DNS. ### Infomaniak (registrar actuel) Le DNS de `edudeploy.com` est chez **Infomaniak** : ```bash dig NS edudeploy.com +short # nsany1.infomaniak.com. # nsany2.infomaniak.com. ``` Il existe un module Caddy DNS pour Infomaniak : - Repository : `github.com/caddy-dns/infomaniak` - Nécessite un **token API Infomaniak** avec droits DNS. ### Implémentation à envisager 1. **Générer un token API Infomaniak** (compte client A ou compte dédié avec accès au domaine). 2. **Builder une image Caddy custom** avec le module : ```dockerfile FROM caddy:2-builder AS builder RUN xcaddy build --with github.com/caddy-dns/infomaniak FROM caddy:2-alpine COPY --from=builder /usr/bin/caddy /usr/bin/caddy ``` 3. **Modifier le `Caddyfile`** pour gérer le wildcard : ```caddy *.studioe5.edudeploy.com { tls { dns infomaniak {env.INFOMANIAK_API_TOKEN} } reverse_proxy resolver:2020 { header_up Host {host} } } ``` 4. **Ajouter le token dans `.env`** et le passer au conteneur Caddy. 5. Supprimer ou ajuster le bloc `:443` actuel qui utilise `on_demand` pour les instances. ### Alternative sans module DNS Obtenir le certificat wildcard manuellement (Certbot DNS-01, acheté, etc.) et le charger dans Caddy : ```caddy *.studioe5.edudeploy.com { tls /data/certs/wildcard.crt /data/certs/wildcard.key reverse_proxy resolver:2020 { header_up Host {host} } } ``` Inconvénient : renouvellement manuel. ## 🔧 Notes techniques - Le conteneur `resolver-vpn` utilise `network_mode: service:resolver` pour partager le netns avec le resolver. - L’agent utilise `tailscaled --tun=userspace-networking` ; le resolver-vpn utilise un vrai TUN (`tailscale0`). - Le `Caddyfile` actuel utilise `tls { on_demand }` pour les instances. En cas de nouvelle rate limit, on peut temporairement remettre `tls internal` dans le bloc `:443` pour valider le flux sans certificat public.