Date: 26/01/2026 Priorite: HAUTE Status: TERMINE Dependance: SSH Connector v1.0 (TERMINE)
Le connecteur SSH de base est fonctionnel. Ces ameliorations le rendront plus puissant pour des cas d'usage avances.
Certains serveurs ne sont accessibles que via un bastion/jump host:
Local -> Bastion (public) -> Serveur cible (prive)
{
"host": "192.168.1.10",
"port": 22,
"username": "admin",
"auth_method": "key",
"private_key_encrypted": "...",
"jump_host": {
"host": "bastion.example.com",
"port": 22,
"username": "jump-user",
"auth_method": "key",
"private_key_encrypted": "..."
}
}
La librairie ssh2 supporte le tunneling:
// Connect to jump host
const jumpConn = new Client();
jumpConn.connect({ host: jumpHost, ... });
jumpConn.on('ready', () => {
// Create tunnel to target
jumpConn.forwardOut('127.0.0.1', 0, targetHost, 22, (err, stream) => {
// Connect to target through tunnel
const targetConn = new Client();
targetConn.connect({ sock: stream, ... });
});
});
Upload/download actuels utilisent cat et heredoc = texte uniquement.
Besoin de transferer des fichiers binaires (images, archives, etc.)
POST /api/ssh/sftp/upload
{
"instance_id": "uuid",
"local_content_base64": "...",
"remote_path": "/path/to/file",
"mode": "644"
}
POST /api/ssh/sftp/download
{
"instance_id": "uuid",
"remote_path": "/path/to/file"
}
Response: { "content_base64": "...", "size": 12345 }
POST /api/ssh/sftp/list
{
"instance_id": "uuid",
"remote_path": "/var/log"
}
Response: { "entries": [{ "name": "...", "type": "file|dir", "size": ..., "mtime": ... }] }
import { SFTPWrapper } from 'ssh2';
conn.sftp((err, sftp: SFTPWrapper) => {
// Upload
const writeStream = sftp.createWriteStream(remotePath);
writeStream.write(buffer);
// Download
const readStream = sftp.createReadStream(remotePath);
// List
sftp.readdir(remotePath, (err, list) => { ... });
});
L'interface actuelle est "one-shot": commande -> attente -> resultat. Pas de shell interactif (vim, htop, logs live, etc.)
┌─────────────┐ WebSocket ┌─────────────┐ SSH PTY ┌─────────────┐
│ Browser │ <----------------> │ WS Server │ <----------------> │ Serveur │
│ xterm.js │ │ (Bun) │ │ distant │
└─────────────┘ └─────────────┘ └─────────────┘
ws://connectors.33800.nowhere84.com/api/ssh/shell
Query: ?instance_id=uuid&token=jwt
Messages:
- Client -> Server: { "type": "input", "data": "ls -la\n" }
- Server -> Client: { "type": "output", "data": "..." }
- Server -> Client: { "type": "resize", "cols": 80, "rows": 24 }
// Bun WebSocket
app.ws('/api/ssh/shell', {
open(ws) {
const { instance_id, token } = ws.data;
// Verify auth
// Connect SSH with PTY
conn.shell({ term: 'xterm-256color', cols: 80, rows: 24 }, (err, stream) => {
stream.on('data', (data) => ws.send({ type: 'output', data: data.toString() }));
ws.onmessage = (msg) => stream.write(msg.data);
});
}
});
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
const term = new Terminal();
const ws = new WebSocket(`wss://connectors.../api/ssh/shell?instance_id=${id}`);
term.onData(data => ws.send(JSON.stringify({ type: 'input', data })));
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'output') term.write(msg.data);
};
| Feature | Complexite | Valeur | Priorite |
|---|---|---|---|
| Double SSH | Moyenne | Haute | 1 |
| SFTP | Moyenne | Moyenne | 2 |
| WebSocket | Haute | Haute | 3 |
xterm - Terminal emulatorxterm-addon-fit - Auto-resizexterm-addon-web-links - Clickable linksLe serveur SSH prod-portainer (192.168.1.12) avait PasswordAuthentication no dans /etc/ssh/sshd_config.
La librairie ssh2 ne pouvait pas s'authentifier par mot de passe.
Solution appliquee: Active PasswordAuthentication yes temporairement.
Solution recommandee: Utiliser l'option "auto-certif" qui genere une cle SSH et l'installe sur le serveur. Cela evite les problemes d'auth par mot de passe et est plus securise.