/** * Module SSH Terminal - Gestion des connexions SSH via WebSocket * Utilise la bibliothèque ssh2 pour établir des connexions SSH */ const { Client } = require('ssh2'); const WebSocket = require('ws'); const jwt = require('jsonwebtoken'); const config = require('./config'); /** * Initialise le serveur WebSocket SSH sur /ws-ssh * @param {http.Server} server - Le serveur HTTP Express */ function initSSHWebSocket(server) { const wssSsh = new WebSocket.Server({ server, path: '/ws-ssh' }); wssSsh.on('connection', (ws, req) => { // Vérifier l'authentification via le token dans l'URL const url = new URL(req.url, 'http://localhost'); const token = url.searchParams.get('token'); if (!token) { ws.send(JSON.stringify({ type: 'error', message: 'Token manquant' })); ws.close(); return; } try { jwt.verify(token, config.jwtSecret); } catch (err) { ws.send(JSON.stringify({ type: 'error', message: 'Token invalide' })); ws.close(); return; } console.log('Nouvelle connexion WebSocket SSH'); let sshClient = null; let sshStream = null; let connected = false; ws.on('message', (data) => { try { const msg = JSON.parse(data.toString()); switch (msg.type) { case 'connect': // Établir la connexion SSH handleConnect(ws, msg, (client, stream) => { sshClient = client; sshStream = stream; connected = true; }); break; case 'input': // Envoyer des données au terminal SSH if (sshStream && connected) { sshStream.write(msg.data); } break; case 'resize': // Redimensionner le terminal if (sshStream && connected) { sshStream.setWindow(msg.rows, msg.cols, 0, 0); } break; case 'disconnect': // Fermer la connexion SSH if (sshClient) { sshClient.end(); } break; default: console.warn('Type de message SSH inconnu:', msg.type); } } catch (err) { console.error('Erreur parsing message SSH:', err.message); } }); ws.on('close', () => { console.log('Connexion WebSocket SSH fermée'); if (sshClient) { sshClient.end(); } }); ws.on('error', (err) => { console.error('Erreur WebSocket SSH:', err.message); if (sshClient) { sshClient.end(); } }); }); console.log('Serveur WebSocket SSH initialisé sur /ws-ssh'); return wssSsh; } /** * Gère la connexion SSH */ function handleConnect(ws, msg, onConnected) { const { host, port, username, password, privateKey } = msg; if (!host || !username) { ws.send(JSON.stringify({ type: 'error', message: 'Hôte et utilisateur requis' })); return; } const sshClient = new Client(); const connConfig = { host: host, port: port || 22, username: username, readyTimeout: 15000, keepaliveInterval: 10000, }; if (privateKey) { connConfig.privateKey = privateKey; } else if (password) { connConfig.password = password; } else { ws.send(JSON.stringify({ type: 'error', message: 'Mot de passe ou clé privée requis' })); return; } ws.send(JSON.stringify({ type: 'status', message: `Connexion à ${username}@${host}:${port || 22}...` })); sshClient.on('ready', () => { ws.send(JSON.stringify({ type: 'connected', message: `Connecté à ${host}` })); sshClient.shell({ term: 'xterm-256color', rows: 24, cols: 80 }, (err, stream) => { if (err) { ws.send(JSON.stringify({ type: 'error', message: `Erreur shell: ${err.message}` })); sshClient.end(); return; } onConnected(sshClient, stream); // Transmettre les données du terminal SSH vers le WebSocket stream.on('data', (data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'output', data: data.toString('base64') })); } }); stream.stderr.on('data', (data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'output', data: data.toString('base64') })); } }); stream.on('close', () => { ws.send(JSON.stringify({ type: 'disconnected', message: 'Session SSH terminée' })); sshClient.end(); }); }); }); sshClient.on('error', (err) => { ws.send(JSON.stringify({ type: 'error', message: `Erreur SSH: ${err.message}` })); }); sshClient.on('end', () => { ws.send(JSON.stringify({ type: 'disconnected', message: 'Connexion SSH fermée' })); }); sshClient.connect(connConfig); } module.exports = { initSSHWebSocket };