ssh_tunnel_proxy 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "commonjs": true,
5
+ "es2021": true
6
+ },
7
+ "extends": "eslint:recommended",
8
+ "overrides": [
9
+ ],
10
+ "parserOptions": {
11
+ "ecmaVersion": "latest"
12
+ },
13
+ "rules": {
14
+ }
15
+ }
package/lib/index.js CHANGED
@@ -17,18 +17,14 @@ License: MIT
17
17
  */
18
18
 
19
19
  const net = require('net');
20
- const { EventEmitter } = require('node:events');
21
20
  const { Client } = require('electron-ssh2');
22
21
 
23
22
  const KeypairStorage = require('./keypair_storage');
24
23
  const NgrokApi = require('./ngrok_service');
25
24
 
26
- // debug logging options
27
- const debug_ssh = false;
28
-
29
25
  const keypairStorage = new KeypairStorage();
30
26
 
31
- class sshTunnelProxy extends EventEmitter {
27
+ class SSHTunnelProxy extends Client {
32
28
 
33
29
  constructor() {
34
30
  super();
@@ -46,14 +42,14 @@ class sshTunnelProxy extends EventEmitter {
46
42
  proxy_ports.forEach(proxy_port => {
47
43
  const server = this.listener[server_name][proxy_port];
48
44
  if (server && server.listening) {
49
- this.debug_en &&this.debug('SSH Server :: closing forward:', proxy_port)
45
+ this.debug_en && this.debug('SSH Server :: closing forward:', proxy_port)
50
46
  server.close();
51
47
  }
52
48
  });
53
49
  }
54
50
 
55
51
  // create forward out on socket connection
56
- setup_ssh_forward(ssh_client, socket, remote_hostname, remote_port) {
52
+ setup_ssh_forward(socket, remote_hostname, remote_port) {
57
53
  const _this = this;
58
54
 
59
55
  // setup stream pipeline when port forward is ready
@@ -61,7 +57,7 @@ class sshTunnelProxy extends EventEmitter {
61
57
 
62
58
  if (err) {
63
59
  _this.emit('debug', err);
64
- _this.debug_en &&_this.debug('socket forward error:', err);
60
+ _this.debug_en && _this.debug('socket forward error:', err);
65
61
  return;
66
62
  }
67
63
 
@@ -77,13 +73,13 @@ class sshTunnelProxy extends EventEmitter {
77
73
  // if socket error, emit error and close stream
78
74
  socket.on('error', (err) => {
79
75
  _this.emit('debug', err);
80
- _this.debug_en &&_this.debug('socket on error:', err);
76
+ _this.debug_en && _this.debug('socket on error:', err);
81
77
  stream.end();
82
78
  });
83
79
  }
84
80
 
85
81
  // create port forward
86
- ssh_client.forwardOut(
82
+ _this.forwardOut(
87
83
  socket.remoteAddress,
88
84
  socket.remotePort,
89
85
  remote_hostname,
@@ -94,7 +90,7 @@ class sshTunnelProxy extends EventEmitter {
94
90
  }
95
91
 
96
92
  // function to setup proxy forwards for ssh tunnel
97
- on_ssh_client_ready(ssh_client, server_name, proxy_ports, resolve) {
93
+ on_ssh_client_ready(server_name, proxy_ports, resolve) {
98
94
 
99
95
  var listeners = 0;
100
96
  if (this.listener[server_name]) this.listener[server_name].isConnected = true;
@@ -108,9 +104,10 @@ class sshTunnelProxy extends EventEmitter {
108
104
  // create local socket server
109
105
  const server = this.listener[server_name][proxy_port];
110
106
  if (server && server.listening) {
111
- this.debug_en &&this.debug('SSH Server :: closing forward:', proxy_port)
107
+ this.debug_en && this.debug('SSH Server :: closing forward 2:', proxy_port)
112
108
  server.close();
113
109
  }
110
+ this.listener[server_name][proxy_port] = null;
114
111
  this.listener[server_name][proxy_port] = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
115
112
 
116
113
  if (this.debug_en) {
@@ -120,11 +117,13 @@ class sshTunnelProxy extends EventEmitter {
120
117
  }
121
118
 
122
119
  // create a proxy forward between local and remote ports
123
- _this.setup_ssh_forward(ssh_client, socket, remote_hostname, remote_port);
120
+ _this.setup_ssh_forward(socket, remote_hostname, remote_port);
124
121
  });
125
122
 
126
123
  // start listening on port
127
124
  try {
125
+ var status_msg = 'SSH Server :: before listen on ' + local_port;
126
+ _this.emit('debug', status_msg);
128
127
  _this.listener[server_name][proxy_port].listen(local_port, () => {
129
128
 
130
129
  // emit server listening on port message
@@ -133,15 +132,15 @@ class sshTunnelProxy extends EventEmitter {
133
132
 
134
133
  // if all listeners have been successfully established, resolve setup connection
135
134
  if (listeners++ >= proxy_ports.length - 1) {
136
- _this.emit('ready', {});
135
+ _this.emit('ssh_tunnel_ready', {});
137
136
  resolve();
138
137
  }
139
138
  });
140
139
  } catch (err) {
141
- _this.debug_en &&_this.debug('listen err:', err);
140
+ _this.debug_en && _this.debug('listen err:', err);
142
141
  }
143
142
  });
144
- };
143
+ }
145
144
 
146
145
  // handle setting up ssh client and proxy forward ports
147
146
  do_ssh_connect(opts) {
@@ -149,7 +148,7 @@ class sshTunnelProxy extends EventEmitter {
149
148
  const _this = this;
150
149
 
151
150
  // create a new ssh client connection with supplied credentials and hostname/port
152
- return new Promise((resolve, reject) => {
151
+ return new Promise((resolve) => {
153
152
 
154
153
  // exit if connection already established, avoid redundant connection on retry
155
154
  if (_this.listener[opts.server_name] && _this.listener[opts.server_name].isConnected) resolve();
@@ -161,46 +160,46 @@ class sshTunnelProxy extends EventEmitter {
161
160
  _this.listener[opts.server_name] = {};
162
161
  }
163
162
 
164
- const ssh_client = new Client();
165
-
166
- ssh_client.on('ready', () => {
167
- _this.on_ssh_client_ready(ssh_client, opts.server_name, opts.proxy_ports, resolve);
163
+ _this.on('ready', () => {
164
+ _this.on_ssh_client_ready(opts.server_name, opts.proxy_ports, resolve);
168
165
  });
169
166
 
170
- ssh_client.on('end', () => {
171
- _this.debug_en &&_this.debug('SSH Client :: end');
167
+ _this.on('end', () => {
168
+ _this.debug_en && _this.debug('SSH Client :: end');
172
169
  _this.close_sockets(opts.server_name, opts.proxy_ports);
173
170
  });
174
171
 
175
- ssh_client.on('close', () => {
176
- _this.debug_en &&_this.debug('SSH Client :: close');
172
+ _this.on('close', () => {
173
+ _this.debug_en && _this.debug('SSH Client :: close');
177
174
  _this.close_sockets(opts.server_name, opts.proxy_ports);
178
175
  });
179
176
 
180
- ssh_client.on('error', err => {
181
- _this.debug_en &&_this.debug('SSH Client :: error :: ' + err);
182
- _this.emit('error', err);
177
+ _this.on('error', err => {
178
+ _this.debug_en && _this.debug('SSH Client :: error :: ' + err);
179
+ _this.emit('debug', err);
183
180
  if (_this.listener[opts.server_name]) _this.listener[opts.server_name].isConnected = false;
184
181
  _this.ssh_retry_connect(opts);
185
182
  resolve();
186
183
  });
187
184
 
188
- ssh_client.on('handshake', negotiated => {
189
- _this.debug_en &&_this.debug('SSH Client :: handshake:', JSON.stringify(negotiated));
185
+ _this.on('handshake', negotiated => {
186
+ _this.debug_en && _this.debug('SSH Client :: handshake:', JSON.stringify(negotiated));
190
187
  });
191
188
 
192
- ssh_client.on('greeting', (message, language) => {
193
- _this.debug_en &&_this.debug('SSH Client :: greeting:', message);
189
+ _this.on('greeting', (message) => {
190
+ _this.debug_en && _this.debug('SSH Client :: greeting:', message);
194
191
  });
195
192
 
196
193
  // initiate ssh client connection to remote host
197
- ssh_client.connect({
194
+ // ssh client debug function for verbose ssh connection details
195
+ const debug_client = (this.debug_ssh) ? (...args) => { console.log(...args); } : null;
196
+ _this.connect({
198
197
  host: opts.host,
199
198
  port: opts.port,
200
199
  username: opts.username,
201
200
  password: opts.password,
202
201
  privateKey: opts.private_key,
203
- debug: _this.debug_client,
202
+ debug: debug_client,
204
203
  keepaliveInterval: 10000
205
204
  });
206
205
 
@@ -222,13 +221,14 @@ class sshTunnelProxy extends EventEmitter {
222
221
  const remote_ports = [];
223
222
  proxy_ports.forEach(proxy_port => {
224
223
  const [local_port, remote_hostname, remote_port] = proxy_port.split(':');
224
+ if (remote_hostname.length < 1) remote_ports.push(remote_port);
225
225
  if (!this.validate_port_number(local_port, whitelist)) local_ports.push(local_port);
226
226
  if (!this.validate_port_number(remote_port, whitelist)) remote_ports.push(remote_port);
227
227
  });
228
228
  if (local_ports.length || remote_ports.length) {
229
229
  const err = new Error('Invalid local forward');
230
230
 
231
- this.debug_en &&this.debug('invalid ports found:\n', JSON.stringify(proxy_ports, null, 2),
231
+ this.debug_en && this.debug('invalid ports found:\n', JSON.stringify(proxy_ports, null, 2),
232
232
  '\n', JSON.stringify(whitelist, null, 2));
233
233
 
234
234
  err.info = {
@@ -241,7 +241,8 @@ class sshTunnelProxy extends EventEmitter {
241
241
  }
242
242
 
243
243
  // on network error, attempt to re-establish connection until 10 retries
244
- ssh_retry_connect = (opts) => {
244
+ //clearTimeout(this._readyTimeout);
245
+ ssh_retry_connect(opts) {
245
246
  const _this = this;
246
247
  const invoke = () => {
247
248
  _this.connectSSH(opts);
@@ -250,9 +251,9 @@ class sshTunnelProxy extends EventEmitter {
250
251
  }
251
252
 
252
253
  // attempt to establish ssh tunnel to server with supplied parameters.
253
- ssh_start_tunnel = async (opts) => {
254
+ async ssh_start_tunnel(opts) {
254
255
  const _this = this;
255
- return new Promise(async (resolve, reject) => {
256
+ async function do_ssh_connect(resolve) {
256
257
 
257
258
  // after successful tunnel setup complete, send events and reset retry counter
258
259
  await _this.do_ssh_connect(opts).then(() => {
@@ -264,18 +265,16 @@ class sshTunnelProxy extends EventEmitter {
264
265
  _this.emit('status', '', 'ready', hostport);
265
266
  _this.retries = 0;
266
267
  resolve();
267
- }, (err) => {
268
- _this.debug_en &&_this.debug('ssh_retry error:', err);
269
- reject();
270
268
  });
271
- });
269
+ }
270
+ return new Promise(do_ssh_connect);
272
271
  }
273
272
 
274
273
  // setup ssh connection parameters and attempt to establish ssh tunnel
275
274
  // todo: if opts have changed while service is running, shutdown current service and restart with new opts
276
275
  async connectSSH(opts, whitelist) {
277
276
 
278
- this.debug_en &&this.debug('connectSSH:\n', JSON.stringify(opts, null, 2));
277
+ this.debug_en && this.debug('connectSSH:\n', JSON.stringify(opts, null, 2));
279
278
 
280
279
  // make deep copy of opts for modification
281
280
  var _opts = JSON.parse(JSON.stringify(opts));
@@ -287,8 +286,8 @@ class sshTunnelProxy extends EventEmitter {
287
286
  try {
288
287
  this.validate_local_forward(_opts.proxy_ports, _opts.whitelist);
289
288
  } catch (err) {
290
- this.emit('error', err);
291
- this.debug_en &&this.debug(err);
289
+ this.emit('debug', err);
290
+ this.debug_en && this.debug(err);
292
291
  return err;
293
292
  }
294
293
 
@@ -307,8 +306,8 @@ class sshTunnelProxy extends EventEmitter {
307
306
  const _this = this;
308
307
  const ngrokApi = new NgrokApi(_opts.ngrok_api);
309
308
  var hostport = await ngrokApi.get_hostport()
310
- .catch((err) => {
311
- _this.debug_en &&_this.debug('ngrok get_hostport connection error');
309
+ .catch(() => {
310
+ _this.debug_en && _this.debug('ngrok get_hostport connection error');
312
311
  if (this.listener[opts.server_name]) this.listener[opts.server_name].isConnected = false;
313
312
  _this.ssh_retry_connect(_opts);
314
313
  });
@@ -322,7 +321,7 @@ class sshTunnelProxy extends EventEmitter {
322
321
  // initiate ssh tunnel, block until tunnel is established or error
323
322
  return await this.ssh_start_tunnel(_opts)
324
323
  .catch((err) => {
325
- this.debug_en &&this.debug('ssh_start_tunnel catch:' + err);
324
+ this.debug_en && this.debug('ssh_start_tunnel catch:' + err);
326
325
  });
327
326
  }
328
327
 
@@ -340,15 +339,12 @@ class sshTunnelProxy extends EventEmitter {
340
339
  keypairStorage.get_public_key_from_keychain(...args);
341
340
  }
342
341
 
343
- // ssh client debug function for verbose ssh connection details
344
- debug_client = (this.debug_ssh) ? (msg) => { console.log(msg); } : null;
345
-
346
342
  debug(...args) { console.log(...args); }
347
343
 
348
344
  }
349
345
 
350
346
  module.exports = {
351
- SSHTunnelProxy:sshTunnelProxy,
352
- KeypairStorage:KeypairStorage,
353
- NgrokApi:NgrokApi
347
+ SSHTunnelProxy: SSHTunnelProxy,
348
+ KeypairStorage: KeypairStorage,
349
+ NgrokApi: NgrokApi
354
350
  }
@@ -23,7 +23,7 @@ class KeypairStorage {
23
23
  generate_and_store_keypair(service_name, account) {
24
24
  const keypair = this.generate_keypair();
25
25
  return new Promise((resolve, reject) => {
26
- keytar.setPassword(service_name, account, keypair.private_key).then((result) => {
26
+ keytar.setPassword(service_name, account, keypair.private_key).then(() => {
27
27
  resolve(keypair.public_key);
28
28
  }, (err) => {
29
29
  reject(err);
package/main.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const process = require('process');
2
2
  const fs = require('fs');
3
3
  const { SSHTunnelProxy, KeypairStorage } = require('./lib/index.js');
4
- const { config } = require('process');
5
4
 
6
5
  const keypairStorage = new KeypairStorage();
7
6
  const homedir = require('os').homedir();
@@ -11,6 +10,7 @@ var configs = null;
11
10
  var host = null;
12
11
  var port = null;
13
12
  var username = null;
13
+ var password = null;
14
14
  var forwardOut = [];
15
15
  var forwardIn = [];
16
16
  var ngrokAPIKey = null;
@@ -108,7 +108,7 @@ function main(args) {
108
108
 
109
109
  // if no config file specified, build config from args
110
110
  if (!configs) {
111
- config = {
111
+ var config = {
112
112
  enabled: true,
113
113
  username: username,
114
114
  password: password,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh_tunnel_proxy",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "obtain ngrok host port from api and establish a ssh connection",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -11,7 +11,6 @@
11
11
  "dependencies": {
12
12
  "@ngrok/ngrok-api": "^0.9.0",
13
13
  "bluebird": "^3.7.2",
14
- "crypto": "^1.0.1",
15
14
  "electron-ssh2": "^0.1.2",
16
15
  "form-data": "^4.0.0",
17
16
  "fs": "^0.0.1-security",
@@ -22,6 +21,7 @@
22
21
  "sshpk": "^1.17.0"
23
22
  },
24
23
  "devDependencies": {
24
+ "eslint": "^8.33.0",
25
25
  "mocha": "^10.2.0",
26
26
  "rewire": "^6.0.0",
27
27
  "sinon": "^15.0.1"
package/test/test.js CHANGED
@@ -6,9 +6,8 @@ to run ssh tunnel api tests edit config.json and provide api keys for each servi
6
6
  */
7
7
 
8
8
  const assert = require('assert');
9
+ const { describe, it } = require('mocha');
9
10
  const fs = require('fs');
10
- const path = require('path');
11
- const sinon = require('sinon');
12
11
 
13
12
  const { SSHTunnelProxy, KeypairStorage, NgrokApi } = require('../lib/index.js');
14
13
 
@@ -136,8 +135,8 @@ describe('Store and retrieve private key in system keychain', function () {
136
135
  });
137
136
  });
138
137
  it('remove key should return true', () => {
139
- return keypairStorage.delete_keypair(service_name, account).then(result => {
140
- assert('should return true');
138
+ return keypairStorage.delete_keypair(service_name, account).then((result) => {
139
+ assert(result, 'should return true');
141
140
  });
142
141
  });
143
142
  it('retrieve stored private key after delete should return null', () => {
@@ -174,7 +173,7 @@ describe('Get ngrok hostport', function () {
174
173
  assert.equal(result_opts.port, '17632');
175
174
  });
176
175
  it('host, port should be obtained from api', async () => {
177
- result = await ngrokApi.get_hostport(opts);
176
+ const result = await ngrokApi.get_hostport(opts);
178
177
  assert(result.host, 'host should exist');
179
178
  assert(result.port, 'port should exist');
180
179
  }, (err) => {
@@ -213,17 +212,3 @@ describe('ssh connection integration test', function () {
213
212
 
214
213
  });
215
214
  */
216
-
217
-
218
- // test generate keypair
219
- var test_generate_keypair = function () {
220
- const keypair = generate_keypair();
221
- console.log('keypair:');
222
- console.log(keypair.public_key);
223
- console.log(keypair.private_key);
224
- var homedir = homedir();
225
- var fname = path.join(homedir, '.ssh', 'zm_id_rsa.pub')
226
- //fs.writeFileSync(fname, keypair.public_key);
227
- fname = path.join(homedir, '.ssh', 'zm_id_rsa')
228
- //fs.writeFileSync(fname, keypair.private_key);
229
- }