Files
manus-dashboard/backend/src/ssh.js
2026-09-05 02:23:30 +00:00

180 lines
4.8 KiB
JavaScript

/**
* 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 };