Files
edubox/SUIVI_VPN_ONDEMAND.md
T
EduBox Dev a414f03a59 feat(agent): v0.3.5 Windows inbound forwarding, UI actions, lifecycle
- Configure tailscale serve automatically for each instance on Windows userspace networking.
- Add local UI buttons: start/stop/reset/delete instances (stop/start preserve volumes).
- Clean shutdown: stop tailscaled and instances, notify server with instance_stopped.
- Restart tailscaled on agent boot using persisted state when pre-auth key is absent.
- Sync instance stopped/deleted status to dashboard (server/lib/websocket.ts).
- Security: include prior authz/scoping changes across API routes, ephemeral pre-auth keys, ACL policy, internal API key.
- Update SUIVI_VPN_ONDEMAND.md and docs/ONBOARDING_CLIENT.md.
- Bump agent version to 0.3.5.
2026-06-25 22:59:09 +00:00

32 KiB
Raw Blame History

Suivi VPN on-demand studioE5 (client A)

Ce qui fonctionne

  1. Agent standalone (mode console / systray)

    • Exécutable : agent/studioE5-agent
    • Config lu depuis <data-dir>/studioE5-config.json
    • Mode console : -no-tray
  2. VPN on-demand dans l'agent

    • Lagent ne démarre plus Tailscale au boot.
    • Le VPN se lance automatiquement à la création/démarrage dune 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 lagent (modèle commercialisable)

    • Lagent démarre sans headscale_url ni headscale_auth_key.
    • Lutilisateur entre seulement un code dactivation.
    • Le serveur envoie la config Headscale, lagent la sauvegarde et démarre le VPN automatiquement.

Blocage levé

Rate limit Lets Encrypt pour edudeploy.com est levé.
Le 2026-06-23 vers 09:35 UTC, Caddy a pu obtenir un certificat Lets 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é :

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 dinstance.

🎯 Validation du flux HTTPS public

Le 2026-06-23 09:39 UTC, le flux complet a été validé :

Client (HTTPS) → Caddy (:443) → resolver (:2020) → Tailnet (100.64.0.8) → agent → WordPress (:8001)

Résultat :

$ 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 Lets 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 :

  • Lagent lançait tailscaled avec --socket=<fichier>.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 <data-dir>/agent.log et des logs tailscaled vers <data-dir>/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 :

.\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 lagent soit online.

Logs caractéristiques :

client -> backend close connection: close tcp 100.64.0.12:8080->100.64.0.11:xxxxx: endpoint not connected

Solution : tailscale serve automatique

Lagent configure automatiquement un proxy TCP pour chaque instance démarrée :

tailscale serve --bg --tcp=<port> tcp://localhost:<port>
Action agent Commande Tailscale
Démarrage dinstance serve --bg --tcp=<port> tcp://localhost:<port>
Arrêt dinstance serve --bg --tcp=<port> off
Suppression dinstance serve --bg --tcp=<port> off
Redémarrage de lagent 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 daction 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êterdocker compose stop (volumes conservés).
  • Démarrerdocker compose start (ou up -d la première fois).
  • Redémarrerdocker compose down -v + recréation (données remises à zéro).
  • Supprimerdocker compose down -v + suppression des fichiers.
  • À la fermeture de lagent, les instances en cours sont arrêtées proprement (stop) et le serveur est notifié (instance_stopped).

Démarrage du VPN après activation

Lagent 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 lagent de test

pgrep -a studioe5-agent

Relancer lagent de test (si besoin)

mkdir -p /tmp/studioe5-test-clienta
cat > /tmp/studioe5-test-clienta/studioE5-config.json <<EOF
{
  "server": "wss://studioe5.edudeploy.com/api/websocket",
  "headscale_url": "https://headscale.studioe5.edudeploy.com",
  "headscale_auth_key": "$(grep HEADSCALE_AUTH_KEY /opt/studioe5-client-a/.env | cut -d= -f2)",
  "node_id": "vps-8fc665eb",
  "data_dir": "/tmp/studioe5-test-clienta"
}
EOF
cd /opt/studioe5-client-a/agent
./studioE5-agent -no-tray -data-dir /tmp/studioe5-test-clienta

Démarrer le VPN manuellement

curl -sS -X POST https://studioe5.edudeploy.com/api/internal/send-to-node \
  -H "Content-Type: application/json" \
  -d '{"nodeId":"vps-8fc665eb","message":{"action":"start_vpn"}}'

Voir les nodes Headscale

cd /opt/studioe5-client-a
docker compose exec -T headscale headscale nodes list studioe5

Tester le resolver (depuis Caddy)

cd /opt/studioe5-client-a
docker exec studioe5-caddy curl -sS -I -H "Host: test-wp-001.studioe5.edudeploy.com" http://resolver:2020/

Tester en HTTPS public (dès que la limite sera levée)

curl -sS -I -L https://test-wp-001.studioe5.edudeploy.com/

🌐 Flux complet testé via lAPI web

Test réalisé le 2026-06-23 en utilisant le compte superadmin :

  1. Authentification NextAuth sur /api/auth/callback/credentials.
  2. Création dinstance via POST /api/instances :
    {
      "nodeId": "vps-8fc665eb",
      "templateId": "wordpress-wordpress-latest",
      "port": 8002
    }
    
    → Instance créée : cmqqgrur20001lw67t2bdgzkg.
  3. Le serveur a automatiquement envoyé laction start au node via WebSocket.
  4. Lagent a démarré le VPN (si besoin), écrit le compose et a lancé les conteneurs WordPress.
  5. Caddy a obtenu un certificat Lets Encrypt pour cmqqgrur20001lw67t2bdgzkg.studioe5.edudeploy.com.
  6. Validation HTTPS :
    curl -sS -I -L https://cmqqgrur20001lw67t2bdgzkg.studioe5.edudeploy.com/
    # => 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 lagent

Lagent 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 davoir installé Tailscale Windows séparément ou davoir les binaires dans tailscale-bin/windows/.
  • Linux : https://studioe5.edudeploy.com/studioE5-agent-v0.3.5

Builder / préparer les binaires

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 lagent 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 dactivation zéro-config (modèle commercialisable)

L’élève/employé na aucune configuration technique à saisir :

  1. Télécharger lagent Windows (studioE5-agent-v0.3.4-windows.zip).
  2. Extraire et lancer studioE5-agent.exe.
  3. Entrer le code dactivation à 6 caractères fourni par l’établissement (affiché dans lUI locale http://localhost:7070).
  4. Lagent contacte le serveur, le serveur vérifie le code et renvoie automatiquement :
    • lidentité de l’élève (studentName)
    • lURL Headscale
    • la clé pré-auth Headscale
  5. Lagent sauvegarde ces informations localement et démarre automatiquement le VPN.
  6. Lagent 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 :

{
  "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 :

.\studioE5-agent.exe -no-tray -data-dir C:\studioE5-agent\data

🔒 Durcissement du code dactivation

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 dactivation 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 dinstance ;
  • 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 lutilisateur 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

{
  "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 dauthentification par nœud

  • Le modèle Node dispose dun champ token unique.
  • Lagent envoie son token dans len-tête Authorization: Bearer <token> lors de la connexion WebSocket.
  • Le serveur rejette toute connexion/register dont le token ne correspond pas au nodeId (fermeture 1008).
  • Lors de lactivation, le serveur génère un token aléatoire (32 octets hex) et le renvoie dans le message activated ; lagent le sauvegarde dans <data-dir>/node.token (permissions 0600).
  • Pour les nœuds existants sans token, le serveur en génère un à la première connexion et lenvoie via set_token.

Endpoint /api/internal/send-to-node

  • Protégé par la variable denvironnement INTERNAL_API_KEY.
  • Requiert len-tête Authorization: Bearer <INTERNAL_API_KEY>.
  • 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 len-tête Authorization: Bearer <INTERNAL_API_KEY>.
  • Le resolver (resolver:2020) ne lutilise 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

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

À lactivation zero-config, le serveur génère désormais une clé pré-auth unique et à usage unique pour chaque agent, au lieu denvoyer la clé réutilisable HEADSCALE_AUTH_KEY.

Avantages :

  • une clé compromise ne permet pas denregistrer dautres nœuds ;
  • traçabilité directe entre une activation et une clé Headscale ;
  • expiration courte (15 min) ;
  • la clé nest 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 lutilisateur studioe5. Fallback sur HEADSCALE_AUTH_KEY si HEADSCALE_API_KEY nest pas configurée.
agent/websocket.go La clé reçue est utilisée immédiatement mais nest 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) :

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 lajouter dans .env :

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 :
    docker compose exec headscale headscale apikeys create -e 87600h
    
  2. Mettre à jour .env :
    HEADSCALE_API_KEY=<nouvelle_clé>
    
  3. Redémarrer le serveur :
    docker compose up -d server
    
  4. Révoquer lancienne clé :
    docker compose exec headscale headscale apikeys expire --id <id_ancienne>
    

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 lapplication 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 <data-dir>/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 lOS des VPS et des postes agents.
  • Mécanisme de mise à jour automatique ou notification de lagent.

Logs daudit

  • Tracer la création / suppression dinstances.
  • Tracer la génération et lusage des codes dactivation.
  • Tracer les actions admin (connexion, création d’élève, démarrage/arrêt VPN).
  • Conservation et consultation des logs daudit.

Backups et reprise dactivité

  • 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 lagent

  • Vérifier lintégrité des binaires Tailscale téléchargés (checksum / signature).
  • Signer lexécutable Windows studioE5-agent.exe pour éviter les alertes Defender.
  • Fournir un hash SHA256 des archives dagent.

RGPD et données personnelles

  • Justifier la conservation des noms/prénoms des élèves.
  • Gérer les droits daccès, la suppression de compte et lexport de données.
  • Définir la durée de conservation des logs et historiques.

Sécurité réseau complémentaire

  • Restreindre laccès à /api/internal/send-to-node par IP source si possible.
  • Vérifier lexposition 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 dinstance).
  • Limitation du nombre dinstances par élève et par établissement.
  • Protection contre les abus sur la génération de codes dactivation.

Tests de sécurité

  • Tests dintrusion légers (agent → agent, accès aux endpoints internes sans clé, accès à une instance dun autre élève).
  • Tests automatisés du flux complet avant chaque release.

📋 Prochaines étapes à faire

Terminé

  • Rate limit Lets Encrypt levé.
  • Flux HTTPS public validé (test-wp-001.studioe5.edudeploy.com).
  • Branche feat/studioe5-vpn-ondemand créée, commit 124543d.
  • Flux complet UI → API → WebSocket → agent → Docker → VPN → Caddy validé.
  • Packager les binaires Tailscale pour Windows (studioE5-agent-v0.3.5-windows.zip).
  • Sécurité authentification du canal serveur → agent (token par nœud, clé API interne, sessions NextAuth sur les routes API).
  • Sécurité durcissement du code dactivation (crypto.randomBytes, expiration 60 min, rate-limiting, invalidation après usage).
  • Sécurité ACL Headscale (isolation agent ↔ agent, resolver → agent autorisé).
  • 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).
  • Agent v0.3.5 forwarding entrant Windows (tailscale serve automatique au démarrage de chaque instance).
  • Agent v0.3.5 UI locale moderne (dashboard, logs, progression, actions dinstance).
  • Agent v0.3.5 cycle de vie des instances (stop/start préservent les volumes, reset/delete effacent).
  • Agent v0.3.5 cleanup au shutdown (arrêt propre de Tailscale et des instances, notification serveur).
  • 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 dun nouveau serveur client + agent générique (option A : URL serveur déterminée à lactivation).
  • 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 daudit (instances, codes dactivation, actions admin).
  • Sécurité backups et reprise dactivité (DB, state Headscale, states agents).
  • Sécurité intégrité et signature de lagent (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 lagent (v0.3.5)

Les logs de lagent sont redirigés vers <data-dir>/agent.log et diffusés en temps réel dans lUI locale (http://localhost:7070) via le WebSocket existant.

Barre de progression (v0.3.5)

Lagent envoie des messages progress au frontend pendant le démarrage dune instance :

Étape Poids
Préparation de lapplication 10 %
Configuration de lapplication 30 %
Application en cours de démarrage 60 %
Connexion sécurisée active 80 %
Finalisation de linstallation 90 %
Application prête 100 %

Boutons daction par instance (v0.3.5)

LUI 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. LURL du serveur cible est déterminée au moment de lactivation, pas hardcodée dans lagent.
    • Pistes : code dactivation résolu par un hub central, code structuré contenant lidentifiant du serveur, ou champ URL serveur saisi dans lUI 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). Lagent doit pouvoir déterminer lURL serveur cible à lactivation (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 dimages À 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 lagent 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. Larchitecture actuelle (un serveur par client + agent zero-config) est déjà compatible avec cette vision, mais le code nest 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 Lets Encrypt par sous-domaine dinstance. 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 dinstances 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 :

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 :
    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 :
    *.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 :

*.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.
  • Lagent 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.