Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket hang up when using HTTPSAgent #815

Closed
tom-drake opened this issue Aug 3, 2019 · 10 comments
Closed

Socket hang up when using HTTPSAgent #815

tom-drake opened this issue Aug 3, 2019 · 10 comments

Comments

@tom-drake
Copy link

Hi, I'm currently trying to use the https agent to automatically tunnel my requests over an ssh tunnel via some external host. For some reason the socket is terminating but only for the HTTPSAgent. I make the same call but with the HTTPAgent and this is successful.

var ssh_config = {
  host: 'ssh.example.com',
  username: 'user',
  privateKey: require('fs').readFileSync('/path/to/private/key')
};

// Works fine
var HTTPAgent = require('ssh2').HTTPAgent;
const httpAgent = new HTTPAgent(ssh_config);
axios({
  url: 'http://api.ipify.org',
  method: 'get',
  httpAgent
});

// Throws an error
var HTTPSAgent = require('ssh2').HTTPSAgent;
const httpsAgent = new HTTPSAgent(ssh_config);
axios({
  url: 'https://api.ipify.org',
  method: 'get',
  httpsAgent
});

The error I see is

Error: socket hang up
    at createHangUpError (_http_client.js:323:15)
    at Channel.socketOnEnd (_http_client.js:426:23)
    at Channel.emit (events.js:194:15)
    at endReadableNT (_stream_readable.js:1103:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)

I'm not sure if I'm missing something here with my config if the https agent needs anything extra. Any help would be appreciated. Thanks

@tom-drake
Copy link
Author

tom-drake commented Aug 3, 2019

The error I posted above is from node v10.15.1
I tried on a different version of node to see if it made any difference. The error from node v12.7.0 is

Error: socket hang up
    at connResetException (internal/errors.js:559:14)
    at Channel.socketOnEnd (_http_client.js:433:23)
    at Channel.emit (events.js:208:15)
    at endReadableNT (_stream_readable.js:1161:12)
    at processTicksAndRejections (internal/process/task_queues.js:77:11)

I couldn't find where the error was coming from outside of these internal node errors. If anyone knows a way I can do some further investigating.

@tom-drake
Copy link
Author

I had a dig into the server logs as I thought it could be that which is disconnecting as the error code is ECONNRESET but it seems the client hung up here

sshd[8953]: Accepted publickey for sshuser from 123.123.123.123 port 48982 ssh2: RSA ...
sshd[8953]: pam_unix(sshd:session): session opened for user sshuser by (uid=0)
systemd-logind[799]: New session 879 of user sshuser.
sshd[8953]: Received disconnect from 123.123.123.123 port 48982:11:
sshd[8953]: Disconnected from user sshuser 123.123.123.123 port 48982
sshd[8953]: pam_unix(sshd:session): session closed for user sshuser
systemd-logind[799]: Removed session 879.

@tom-drake
Copy link
Author

@mscdex have you any idea what could be causing this or know of somewhere I can look? I can try put a PR in for a fix if its a bug but I'm not sure where to start looking.

@mscdex
Copy link
Owner

mscdex commented Aug 20, 2019

I don't know offhand. Is it happening with all servers or just this one particular server?

@tom-drake
Copy link
Author

I'll give it a try against a few servers with different distros and see whats happening. I assume you are talking about the SSH target?

@mscdex
Copy link
Owner

mscdex commented Aug 20, 2019

I assume you are talking about the SSH target?

Yes.

@tom-drake
Copy link
Author

I have tried the following where the httpsAgent fails but the httpAgent is fine:
The original server was ubuntu-18.04, I didn't happen to get its ssh/ssl versions before tearing it down.

distro: ubuntu-16.04
ssh/ssl: OpenSSH_7.2p2 Ubuntu-4ubuntu2.8, OpenSSL 1.0.2g 1 Mar 2016
server log:

Aug 23 20:31:44 proxy sshd[3869]: Accepted publickey for root from 123.123.123.123 port 42096 ssh2: RSA ...
Aug 23 20:31:44 proxy sshd[3869]: pam_unix(sshd:session): session opened for user root by (uid=0)
Aug 23 20:31:44 proxy sshd[3869]: Received disconnect from 123.123.123.123 port 42096:11:
Aug 23 20:31:44 proxy sshd[3869]: Disconnected from 123.123.123.123 port 42096
Aug 23 20:31:44 proxy sshd[3869]: pam_unix(sshd:session): session closed for user root

distro: ubuntu-16.04
ssh/ssl: OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017
server log:

Aug 23 20:42:40 proxy sshd[1427]: Accepted publickey for root from 123.123.123.123 port 42212 ssh2: RSA ...
Aug 23 20:42:40 proxy sshd[1427]: pam_unix(sshd:session): session opened for user root by (uid=0)
Aug 23 20:42:40 proxy sshd[1427]: Received disconnect from 123.123.123.123 port 42212:11:
Aug 23 20:42:40 proxy sshd[1427]: Disconnected from 123.123.123.123 port 42212
Aug 23 20:42:40 proxy sshd[1427]: pam_unix(sshd:session): session closed for user root

@tom-drake
Copy link
Author

I've done a lot of digging for this and I think i've figured out whats going on. The stream decorator is fine for http as there's no TLS encryption, however it looks like the createConnection function needs to wrap the stream with tls.connect for https connections. I'm going to fork the repo and have a play locally and I'll get a PR raised.

@acolby
Copy link

acolby commented Jun 5, 2020

Decided to implement a work around. Per @tom-drake's pr (thank you!).

You can add the following code to you project and import it, rather then using ssh2.HttpAgent.

Requiring this code and calling new code.HttpsAgent(config) returns a working httpsAgent that will work with axios.

const HttpAgent = require('http').Agent;
const HttpsAgent = require('https').Agent;
const inherits = require('util').inherits;
const tls = require('tls');
const Client = require('ssh2').Client;

[HttpAgent, HttpsAgent].forEach((ctor) => {
  function SSHAgent(connectCfg, agentOptions) {
    ctor.call(this, agentOptions);
    this._connectCfg = connectCfg;
    this._defaultSrcIP = (agentOptions && agentOptions.srcIP) || 'localhost';
    this._streamDecorator = ctor === HttpAgent ? decorateHttpStream : decorateHttpsStream;
  }
  inherits(SSHAgent, ctor);

  SSHAgent.prototype.createConnection = createConnection;

  exports[ctor === HttpAgent ? 'HttpAgent' : 'HttpsAgent'] = SSHAgent;
});

function createConnection(options, cb) {
  const srcIP = (options && options.localAddress) || this._defaultSrcIP;
  const srcPort = (options && options.localPort) || 0;
  const dstIP = options.host;
  const dstPort = options.port;

  const client = new Client();
  let triedForward = false;
  client.on('ready', () => {
    client.forwardOut(srcIP, srcPort, dstIP, dstPort, (err, stream) => {
      triedForward = true;
      if (err) {
        client.end();
        return cb(err);
      }
      stream.once('close', client.end);

      return cb(null, this._streamDecorator(stream, options));
    });
  }).on('error', cb).on('close', () => {
    if (!triedForward) cb(new Error('Unexpected connection loss'));
  }).connect(this._connectCfg);
}

function decorateHttpStream(stream, options) {
  stream.setKeepAlive = () => {};
  stream.setNoDelay = () => {};
  stream.setTimeout = () => {};
  stream.ref = () => {};
  stream.unref = () => {};
  stream.destroySoon = stream.destroy;
  return stream;
}

function decorateHttpsStream(stream, options) {
  options.socket = stream;
  return tls.connect(options);
}

@mscdex
Copy link
Owner

mscdex commented Oct 7, 2020

This should be resolved now with the ssh2 rewrite. For more details see here.

@mscdex mscdex closed this as completed Oct 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants