Обратный SSH-туннель NodeJS: не удается привязаться к serveo.net: 80

#javascript #node.js #ssh #ssh-tunnel #ssh2

#javascript #node.js #ssh #ssh-туннель #ssh2

Вопрос:

Контекст

Не так давно я открыл для себя отличный сервис под названием Serveo. Это позволяет мне предоставлять доступ к моим локальным приложениям в Интернете с помощью обратного SSH-туннелирования.

например, соединения, на которые https://abc.serveo.net пересылаются http://localhost:3000 на моем компьютере.

Для этого они не требуют установки клиента, и я могу просто ввести это в командной строке:

 ssh -R 80:localhost:3000 serveo.net
  

где 80 находится удаленный порт на serveo.net к которому я хочу привязаться, и localhost:3000 это локальный адрес для моего приложения.

Если я просто наберу 80 с левой стороны, Serveo ответит, Forwarding HTTP traffic from https://xxxx.serveo.net где xxxx находится доступный поддомен с поддержкой https.

Однако, если я введу другой порт, например, 59000 , приложение будет доступно через serveo.net:59000 , но без SSL.

Проблема

Теперь я хотел бы сделать это с помощью NodeJS, чтобы автоматизировать все в инструменте, который я создаю для своих коллег и партнеров моей компании, чтобы им не нужно было беспокоиться об этом или иметь SSH-клиент на своем компьютере. Я использую модуль узла SSH2.

Вот пример рабочего кода, использующего пользовательскую конфигурацию порта (здесь, 59000 ), с приложением, прослушивающим http://localhost:3000 :

 /**
 * Want to try it out?
 * Go to https://github.com/blex41/demo-ssh2-tunnel
 */
const Client = require("ssh2").Client; // To communicate with Serveo
const Socket = require("net").Socket; // To accept forwarded connections (native module)

// Create an SSH client
const conn = new Client();
// Config, just like the second example in my question
const config = {
  remoteHost: "",
  remotePort: 59000,
  localHost: "localhost",
  localPort: 3000
};

conn
  .on("ready", () => {
    // When the connection is ready
    console.log("Connection ready");
    // Start an interactive shell session
    conn.shell((err, stream) => {
      if (err) throw err;
      // And display the shell output (so I can see how Serveo responds)
      stream.on("data", data => {
        console.log("SHELL OUTPUT: "   data);
      });
    });
    // Request port forwarding from the remote server
    conn.forwardIn(config.remoteHost, config.remotePort, (err, port) => {
      if (err) throw err;
      conn.emit("forward-in", port);
    });
  })
  // ===== Note: this part is irrelevant to my problem, but here for the demo to work
  .on("tcp connection", (info, accept, reject) => {
    console.log("Incoming TCP connection", JSON.stringify(info));
    let remote;
    const srcSocket = new Socket();
    srcSocket
      .on("error", err => {
        if (remote === undefined) reject();
        else remote.end();
      })
      .connect(config.localPort, config.localPort, () => {
        remote = accept()
          .on("close", () => {
            console.log("TCP :: CLOSED");
          })
          .on("data", data => {
            console.log(
              "TCP :: DATA: "  
              data
              .toString()
              .split(/n/g)
              .slice(0, 2)
              .join("n")
            );
          });
        console.log("Accept remote connection");
        srcSocket.pipe(remote).pipe(srcSocket);
      });
  })
  // ===== End Note
  // Connect to Serveo
  .connect({
    host: "serveo.net",
    username: "johndoe",
    tryKeyboard: true
  });

// Just for the demo, create a server listening on port 3000
// Accessible both on:
// http://localhost:3000
// https://serveo.net:59000
const http = require("http"); // native module
http
  .createServer((req, res) => {
    res.writeHead(200, {
      "Content-Type": "text/plain"
    });
    res.write("Hello world!");
    res.end();
  })
  .listen(config.localPort);
  

Это работает нормально, я могу получить доступ к своему приложению на http://serveo.net:59000 . Но он не поддерживает HTTPS, что является одним из моих требований. Если я хочу HTTPS, мне нужно установить порт на 80 и оставить удаленный хост пустым, как обычная команда SSH, приведенная выше, чтобы Servo назначил мне доступный поддомен:

 // equivalent to `ssh -R 80:localhost:3000 serveo.net`
const config = {
  remoteHost: "",
  remotePort: 80,
  localHost: "localhost",
  localPort: 3000
};
  

Однако это выдает ошибку:

 Error: Unable to bind to :80
at C:workspacedemo-ssh2-tunnelnode_modulesssh2libclient.js:939:21
at SSH2Stream.<anonymous> (C:workspacedemo-ssh2-tunnelnode_modulesssh2libclient.js:628:24)
at SSH2Stream.emit (events.js:182:13)
at parsePacket (C:workspacedemo-ssh2-tunnelnode_modulesssh2-streamslibssh.js:3851:10)
at SSH2Stream._transform (C:workspacedemo-ssh2-tunnelnode_modulesssh2-streamslibssh.js:693:13)
at SSH2Stream.Transform._read (_stream_transform.js:190:10)
at SSH2Stream._read (C:workspacedemo-ssh2-tunnelnode_modulesssh2-streamslibssh.js:252:15)
at SSH2Stream.Transform._write (_stream_transform.js:178:12)
at doWrite (_stream_writable.js:410:12)
at writeOrBuffer (_stream_writable.js:394:5)
  

Я пробовал много вещей без какого-либо успеха. Если у кого-нибудь есть идея о том, что может быть не так в моем примере, я буду очень благодарен. Спасибо!

Ответ №1:

OpenSSH по умолчанию использует «localhost» для удаленного хоста, когда он не указан. Вы также можете убедиться в этом, проверив вывод отладки из клиента OpenSSH, добавив -vvv в командную строку. Вы должны увидеть строку типа:

 debug1: Remote connections from LOCALHOST:80 forwarded to local address localhost:3000
  

Если вы имитируете это, установив config.remoteHost = 'localhost' в своем JS-коде, вы должны получить тот же результат, что и клиент OpenSSH.

Комментарии:

1. Спасибо тебе @mscdex, вот и все! Я пробовал другие значения здесь, но, я думаю, никогда localhost . Вы спасли мой день