【问题标题】:Connecting to remote SSH server (via Node.js/html5 console)连接到远程 SSH 服务器(通过 Node.js/html5 控制台)
【发布时间】:2016-12-05 23:51:54
【问题描述】:

我一直在网上搜寻一个我认为很简单的问题。我的目标是直截了当的。我想使用 Node.js 模块构建一个简单的基于 Web 的 SSH 客户端。如果我想连接到节点服务器本身,我已经找到了几个选项,但似乎找不到任何连接到远程服务器的示例。

基本上我正在寻找的结果是这样的工作流程:连接到网络服务器->单击服务器列表中的服务器名称->进入我单击的服务器的 SSH 会话

我发现的唯一与我正在寻找的东西很接近的东西是guacamole。但是,我不想使用鳄梨酱,因为我希望这个应用程序独立于操作系统。目前我正在 Windows 10 平台上构建它,并在完成后将其移植到 Fedora。

我为creating an SSH terminal 找到了本教程。然而,这一切只是创建(或尝试创建)与本地系统的 SSH 连接。

另一个看起来非常棒的选项是tty.js。唉,底线与上述教程相同。该模块只允许您连接到 node.js 服务器,而不是远程服务器。

有人知道实现这一目标的可能途径吗?

【问题讨论】:

    标签: javascript node.js windows html ssh


    【解决方案1】:

    与上述答案相同,但实际上使用的是现代语法和库

    const express = require('express');
    const app = express();
    const http = require('http').Server(app);
    const io = require('socket.io')(http, {
      cors: {
        origin: "*"
      }
    });
    app.set('view engine', 'ejs');
    app.use(express.urlencoded({
      extended: false,
      limit: '150mb'
    }));
    app.use(express.static(__dirname + '/public'));
    app.use('/xterm.css', express.static(require.resolve('xterm/css/xterm.css')));
    app.use('/xterm.js', express.static(require.resolve('xterm')));
    app.use('/xterm-addon-fit.js', express.static(require.resolve('xterm-addon-fit')));
    
    const SSHClient = require('ssh2').Client;
    
    app.get('/', (req, res) => {
      // res.sendFile(__dirname + '/index.html');
      res.render('index');
      // I am using ejs as my templating engine but HTML file work just fine.
    });
    
    io.on('connection', function(socket) {
      var conn = new SSHClient();
      conn.on('ready', function() {
        socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n');
        conn.shell(function(err, stream) {
          if (err)
            return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n');
          socket.on('data', function(data) {
            stream.write(data);
          });
          stream.on('data', function(d) {
            socket.emit('data', d.toString('binary'));
          }).on('close', function() {
            conn.end();
          });
        });
      }).on('close', function() {
        socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n');
      }).on('error', function(err) {
        socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n');
      }).connect({
        host: '192.168.0.103',
        port: 22,
        username: 'kali',
        password: 'kali'
      });
    });
    
    http.listen(3000, () => {
      console.log('Listening on http://localhost:3000');
    });
    * {
      padding: 0%;
      margin: 0%;
      box-sizing: border-box;
    }
    
    body {
      font-family: Helvetica, sans-serif, arial;
      font-size: 1em;
      color: #111;
    }
    
    h1 {
      text-align: center;
    }
    
    #terminal-container {
      width: 960px;
      height: 600px;
      margin: 0 auto;
      padding: 2px;
    }
    
    #terminal-container .terminal {
      background-color: #111;
      color: #fafafa;
      padding: 2px;
    }
    
    #terminal-container .terminal:focus .terminal-cursor {
      background-color: #fafafa;
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>SSH SERVER</title>
      <link rel="stylesheet" href="/xterm.css" />
      <script defer src="/xterm.js"></script>
      <script defer src="/xterm-addon-fit.js"></script>
      <script defer src="/socket.io/socket.io.js"></script>
      <script defer src='/js/app.js'></script>
      <link rel='stylesheet' href='/css/main.css'>
    </head>
    
    <body>
      <h3>WebSSH</h3>
      <div id="terminal-container"></div>
      <script>
      // PLEASE USE A SEPERATE FILE FOR THE JS and defer it
      // like the above app.js file 
        window.addEventListener('load', function() {
          const terminalContainer = document.getElementById('terminal-container');
          const term = new Terminal({
            cursorBlink: true
          });
          const fitAddon = new FitAddon.FitAddon();
          term.loadAddon(fitAddon);
          term.open(terminalContainer);
          fitAddon.fit();
    
          const socket = io() //.connect();
          socket.on('connect', function() {
            term.write('\r\n*** Connected to backend ***\r\n');
          });
    
          // Browser -> Backend
          term.onKey(function(ev) {
            socket.emit('data', ev.key);
          });
    
          // Backend -> Browser
          socket.on('data', function(data) {
            term.write(data);
          });
    
          socket.on('disconnect', function() {
            term.write('\r\n*** Disconnected from backend ***\r\n');
          });
        }, false);
      </script>
    </body>
    
    </html>

    【讨论】:

    • 这里怎么传pem?
    • @ShabbirDhangot 在 SSHClient connect 参数中使用 privateKey 而不是 password
    【解决方案2】:

    只需将更新的代码添加到 @mscdex 好答案,因为这些库多年来发生了变化。

    图书馆:

    npm install express socket.io ssh2 xterm xterm-addon-fit
    

    index.html:

    <html>
      <head>
        <title>SSH Terminal</title>
        <link rel="stylesheet" href="/xterm.css" />
        <script src="/xterm.js"></script>
        <script src="/xterm-addon-fit.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script>
          window.addEventListener('load', function() {
            var terminalContainer = document.getElementById('terminal-container');
            const term = new Terminal({ cursorBlink: true });        
            const fitAddon = new FitAddon.FitAddon();
            term.loadAddon(fitAddon);
            term.open(terminalContainer);
            fitAddon.fit();
    
            var socket = io() //.connect();
            socket.on('connect', function() {
              term.write('\r\n*** Connected to backend ***\r\n');
            });
    
            // Browser -> Backend
            term.onKey(function (ev) {
              socket.emit('data', ev.key);
            });
    
            // Backend -> Browser
            socket.on('data', function(data) {
              term.write(data);
            });
    
            socket.on('disconnect', function() {
              term.write('\r\n*** Disconnected from backend ***\r\n');
            });
          }, false);
        </script>
        <style>
          body {
            font-family: helvetica, sans-serif, arial;
            font-size: 1em;
            color: #111;
          }
          h1 {
            text-align: center;
          }
          #terminal-container {
            width: 960px;
            height: 600px;
            margin: 0 auto;
            padding: 2px;
          }
          #terminal-container .terminal {
            background-color: #111;
            color: #fafafa;
            padding: 2px;
          }
          #terminal-container .terminal:focus .terminal-cursor {
            background-color: #fafafa;
          }
        </style>
      </head>
      <body>
        <h3>WebSSH</h3>
        <div id="terminal-container"></div>
      </body>
    </html>
    

    server.js:

    var fs = require('fs');
    var path = require('path');
    var server = require('http').createServer(onRequest);
    
    var io = require('socket.io')(server);
    var SSHClient = require('ssh2').Client;
    
    // Load static files into memory
    var staticFiles = {};
    var basePath = path.join(require.resolve('xterm'), '..');
    staticFiles['/xterm.css'] = fs.readFileSync(path.join(basePath, '../css/xterm.css'));
    staticFiles['/xterm.js'] = fs.readFileSync(path.join(basePath, 'xterm.js'));
    basePath = path.join(require.resolve('xterm-addon-fit'), '..');
    staticFiles['/xterm-addon-fit.js'] = fs.readFileSync(path.join(basePath, 'xterm-addon-fit.js'));
    staticFiles['/'] = fs.readFileSync('index.html');
    
    // Handle static file serving
    function onRequest(req, res) {
      var file;
      if (req.method === 'GET' && (file = staticFiles[req.url])) {
        res.writeHead(200, {
          'Content-Type': 'text/'
            + (/css$/.test(req.url)
            ? 'css'
            : (/js$/.test(req.url) ? 'javascript' : 'html'))
        });
        return res.end(file);
      }
      res.writeHead(404);
      res.end();
    }
    
    io.on('connection', function(socket) {
      var conn = new SSHClient();
      conn.on('ready', function() {
        socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n');
        conn.shell(function(err, stream) {
          if (err)
            return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n');
          socket.on('data', function(data) {
            stream.write(data);
          });
          stream.on('data', function(d) {
            socket.emit('data', d.toString('binary'));
          }).on('close', function() {
            conn.end();
          });
        });
      }).on('close', function() {
        socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n');
      }).on('error', function(err) {
        socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n');
      }).connect({
        host: 'domain.tld',
        port: 22,
        username: 'root',
        privateKey: require('fs').readFileSync('path/to/keyfile')
      });
    });
    
    let port = 8000;
    console.log('Listening on port', port)
    server.listen(port);
    

    【讨论】:

    • 您不应该使用onKey 事件,它只用于低级别事件访问,很少需要。而是使用onDataonBinary,它们可以很好地将所有内容包装成用于 IO 接收器的字节(包括鼠标报告)。
    【解决方案3】:

    这很容易通过 ssh2xtermsocket.io 等模块实现。

    这是一个例子:

    1. npm install ssh2 xterm socket.io
    2. 创建index.html:
    <html>
      <head>
        <title>SSH Terminal</title>
        <link rel="stylesheet" href="/src/xterm.css" />
        <script src="/src/xterm.js"></script>
        <script src="/addons/fit/fit.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script>
          window.addEventListener('load', function() {
            var terminalContainer = document.getElementById('terminal-container');
            var term = new Terminal({ cursorBlink: true });
            term.open(terminalContainer);
            term.fit();
    
            var socket = io.connect();
            socket.on('connect', function() {
              term.write('\r\n*** Connected to backend***\r\n');
    
              // Browser -> Backend
              term.on('data', function(data) {
                socket.emit('data', data);
              });
    
              // Backend -> Browser
              socket.on('data', function(data) {
                term.write(data);
              });
    
              socket.on('disconnect', function() {
                term.write('\r\n*** Disconnected from backend***\r\n');
              });
            });
          }, false);
        </script>
        <style>
          body {
            font-family: helvetica, sans-serif, arial;
            font-size: 1em;
            color: #111;
          }
          h1 {
            text-align: center;
          }
          #terminal-container {
            width: 960px;
            height: 600px;
            margin: 0 auto;
            padding: 2px;
          }
          #terminal-container .terminal {
            background-color: #111;
            color: #fafafa;
            padding: 2px;
          }
          #terminal-container .terminal:focus .terminal-cursor {
            background-color: #fafafa;
          }
        </style>
      </head>
      <body>
        <div id="terminal-container"></div>
      </body>
    </html>
    
    1. 创建server.js:
    var fs = require('fs');
    var path = require('path');
    var server = require('http').createServer(onRequest);
    
    var io = require('socket.io')(server);
    var SSHClient = require('ssh2').Client;
    
    // Load static files into memory
    var staticFiles = {};
    var basePath = path.join(require.resolve('xterm'), '..');
    [ 'addons/fit/fit.js',
      'src/xterm.css',
      'src/xterm.js'
    ].forEach(function(f) {
      staticFiles['/' + f] = fs.readFileSync(path.join(basePath, f));
    });
    staticFiles['/'] = fs.readFileSync('index.html');
    
    // Handle static file serving
    function onRequest(req, res) {
      var file;
      if (req.method === 'GET' && (file = staticFiles[req.url])) {
        res.writeHead(200, {
          'Content-Type': 'text/'
                          + (/css$/.test(req.url)
                             ? 'css'
                             : (/js$/.test(req.url) ? 'javascript' : 'html'))
        });
        return res.end(file);
      }
      res.writeHead(404);
      res.end();
    }
    
    io.on('connection', function(socket) {
      var conn = new SSHClient();
      conn.on('ready', function() {
        socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n');
        conn.shell(function(err, stream) {
          if (err)
            return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n');
          socket.on('data', function(data) {
            stream.write(data);
          });
          stream.on('data', function(d) {
            socket.emit('data', d.toString('binary'));
          }).on('close', function() {
            conn.end();
          });
        });
      }).on('close', function() {
        socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n');
      }).on('error', function(err) {
        socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n');
      }).connect({
        host: '192.168.100.105',
        username: 'foo',
        password: 'barbaz'
      });
    });
    
    server.listen(8000);
    
    1. server.js 中编辑传递给.connect() 的SSH 服务器配置
    2. node server.js
    3. 在浏览器中访问http://localhost:8000

    【讨论】:

    • 老兄,你绝对摇滚! :)
    • 不会碰巧有类似的 RDP 和 VNC 吗? :)
    • 快速搜索 vnc 出现 this。它有点旧,可以稍微优化一下,但它可能仍然有效。就 RDP 而言,我还没有看到节点的 RDP 实现/绑定。
    • 是的,这与我在 VNC 中找到的相同。我最终会搞砸的。对于 RDP,我认为这会起作用 github.com/citronneur/node-rdpjs,但我将不得不找到一些比 GIT 页面上更好的文档。
    • @user2058037 你必须像url.parse(req.url, true)一样解析req.url。然后使用结果对象中的.pathname 而不是req.url 进行staticFiles[] 查找。
    【解决方案4】:

    也试试 noVnc。 但是,在xterm.js 的页面中稍加挖掘就会发现其他解决方案,例如

    WebSSH2

    【讨论】:

      猜你喜欢
      • 2023-03-08
      • 1970-01-01
      • 2019-09-15
      • 2019-03-30
      • 1970-01-01
      • 2017-01-01
      • 2020-07-05
      • 2011-02-15
      • 2020-06-17
      相关资源
      最近更新 更多