ssh_tunnel_proxy 1.2.11 → 2.0.3
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/README.md +86 -5
- package/dist/cjs/index.d.ts +65 -0
- package/dist/cjs/index.js +486 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/keypair_storage.d.ts +12 -0
- package/dist/cjs/keypair_storage.js +120 -0
- package/dist/cjs/keypair_storage.js.map +1 -0
- package/dist/cjs/ngrok_service.d.ts +11 -0
- package/dist/cjs/ngrok_service.js +40 -0
- package/dist/cjs/ngrok_service.js.map +1 -0
- package/dist/cjs/ssh2-node.d.ts +1 -0
- package/dist/cjs/ssh2-node.js +188 -0
- package/dist/cjs/ssh2-node.js.map +1 -0
- package/dist/esm/index.d.ts +65 -0
- package/dist/esm/index.js +481 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/keypair_storage.d.ts +12 -0
- package/dist/esm/keypair_storage.js +117 -0
- package/dist/esm/keypair_storage.js.map +1 -0
- package/dist/esm/ngrok_service.d.ts +11 -0
- package/dist/esm/ngrok_service.js +37 -0
- package/dist/esm/ngrok_service.js.map +1 -0
- package/dist/esm/ssh2-node.d.ts +1 -0
- package/dist/esm/ssh2-node.js +184 -0
- package/dist/esm/ssh2-node.js.map +1 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/keypair_storage.d.ts +12 -0
- package/dist/types/ngrok_service.d.ts +11 -0
- package/dist/types/ssh2-node.d.ts +1 -0
- package/package.json +39 -9
- package/ssh2-node +2 -181
- package/.eslintrc.json +0 -15
- package/.vscode/launch.json +0 -34
- package/lib/index.js +0 -489
- package/lib/keypair_storage.js +0 -76
- package/lib/ngrok_service.js +0 -47
- package/test/test.js +0 -214
- package/test/test_remote_exec.js +0 -217
package/README.md
CHANGED
|
@@ -7,16 +7,20 @@ In addition to the node api, a command line function called ssh-node2 is include
|
|
|
7
7
|
|
|
8
8
|
### Command line examples
|
|
9
9
|
|
|
10
|
+
Initialize ssh2 command:
|
|
11
|
+
```
|
|
12
|
+
npm i ssh_tunnel_proxy -g
|
|
13
|
+
```
|
|
10
14
|
Connect to remote host and establish local forwards:
|
|
11
15
|
|
|
12
16
|
```
|
|
13
|
-
|
|
17
|
+
ssh2-node -i ~/.ssh/<private_key> -L 8180:192.168.1.1:80 <username>@<hostname>
|
|
14
18
|
```
|
|
15
19
|
|
|
16
|
-
Connect to host
|
|
20
|
+
Connect to host with parameters stored in ~/.config/ssh_tunnel_proxy/config.json:
|
|
17
21
|
|
|
18
22
|
```
|
|
19
|
-
|
|
23
|
+
ssh2-node rh2
|
|
20
24
|
```
|
|
21
25
|
|
|
22
26
|
default config file, located at:
|
|
@@ -35,11 +39,88 @@ default config file, located at:
|
|
|
35
39
|
}]
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
Execute
|
|
42
|
+
Execute command on remote host
|
|
39
43
|
```
|
|
40
|
-
|
|
44
|
+
ssh2-node rh2 "ls -all"
|
|
41
45
|
```
|
|
42
46
|
|
|
47
|
+
### List of command-line options (compatible with original ssh2 command):
|
|
48
|
+
```
|
|
49
|
+
Usage: ssh2-node [options] [userhost] [exec...]
|
|
50
|
+
|
|
51
|
+
Nodejs ssh2 command line client
|
|
52
|
+
|
|
53
|
+
Arguments:
|
|
54
|
+
userhost Connect to hostname in config.
|
|
55
|
+
exec Command to exec on remote host (must be in quotes)
|
|
56
|
+
|
|
57
|
+
Options:
|
|
58
|
+
-V, --version output the version number
|
|
59
|
+
-2 --protocolv2 [protocolv2] Forces ssh to try protocol version 2 only.
|
|
60
|
+
-4 --ipv4only [ipv4only] Forces ssh to use IPv4 addresses only.
|
|
61
|
+
-6 --ipv6only [ipv6only] Forces ssh to use IPv6 addresses only.
|
|
62
|
+
-A --forward_enable [forward_enable] Enables forwarding of the authentication agent
|
|
63
|
+
connection.
|
|
64
|
+
-a --forward_disable [forward_disable] Disables forwarding of the authentication agent
|
|
65
|
+
connection.
|
|
66
|
+
-b --bind [bind] bind_address
|
|
67
|
+
-C --compress [compress] Requests compression of all data
|
|
68
|
+
-c --cipher [cipher] cipher_spec
|
|
69
|
+
-D --dynamic_forward [dynamic_forward] Specifies a local 'dynamic' application-level
|
|
70
|
+
port forwarding.
|
|
71
|
+
-e --escape [escape] escape_char Sets the escape character for
|
|
72
|
+
sessions with a pty.
|
|
73
|
+
-F --config [config] configfile
|
|
74
|
+
-f --background [background] Requests ssh to go to background just before
|
|
75
|
+
command execution.
|
|
76
|
+
-g --local_forward_remote [local_forward_remote] Allows remote hosts to connect to local forwarded
|
|
77
|
+
ports.
|
|
78
|
+
-I --smartcard [smartcard] smartcard_device Specify the device ssh.
|
|
79
|
+
-i --identity [identity] Private key filename.
|
|
80
|
+
-K --gssapi_auth [gssapi_auth] Enables GSSAPI-based authentication and
|
|
81
|
+
forwarding (delegation) of GSSAPI credentials to
|
|
82
|
+
the server.
|
|
83
|
+
-k --gssapi_disable [gssapi_disable] Disables forwarding (delegation) of GSSAPI
|
|
84
|
+
credentials to the server.
|
|
85
|
+
-L --local_forward_out [local_forward_out...] bind_address:port:host:hostport
|
|
86
|
+
-l --login_name [login_name] login_name
|
|
87
|
+
-M --master_mode [master_mode] Places the ssh client into 'master' mode for
|
|
88
|
+
connection sharing. Multiple -M options
|
|
89
|
+
-m --mac_spec [mac_spec] mac_spec
|
|
90
|
+
-N --no_exec [no_exec] Do not execute a remote command.
|
|
91
|
+
-n --redirect_stdin [redirect_stdin] Redirects stdin from /dev/null
|
|
92
|
+
-O --ctl_cmd [ctl_cmd] ctl_cmd
|
|
93
|
+
-o --option [option] option
|
|
94
|
+
-p --port [port] port Port to connect to on the remote host.
|
|
95
|
+
-q --quiet [quiet] Quiet mode. Causes most warning and diagnostic
|
|
96
|
+
messages to be suppressed.
|
|
97
|
+
-R --local_forward_in [local_forward_in...] bind_address:port:host:hostport
|
|
98
|
+
-S --ctl_path [ctl_path] ctl_path Specifies the location of a control
|
|
99
|
+
socket for connection sharing.
|
|
100
|
+
-s --subsystem [subsystem] May be used to request invocation of a subsystem
|
|
101
|
+
on the remote system.
|
|
102
|
+
-T --disable_tty [disable_tty] Disable pseudo-tty allocation.
|
|
103
|
+
-t --force_tty [force_tty] Force pseudo-tty allocation.
|
|
104
|
+
-V --version [version] Display the version number and exit.
|
|
105
|
+
-v --verbose [verbose] Verbose mode. Causes ssh to print debugging
|
|
106
|
+
messages about its progress.
|
|
107
|
+
-W --forward_stdin_stdout [forward_stdin_stdout] host:port Requests that standard input and output
|
|
108
|
+
on the client be forwarded to host on port ver
|
|
109
|
+
the secure channel.
|
|
110
|
+
-w --tunnel [tunnel] Requests tunnel device forwarding with the
|
|
111
|
+
specified tun(4) devices between the client
|
|
112
|
+
(local_tun) and the server (remote_tun).
|
|
113
|
+
-X --x11 [x11] Enables X11 forwarding.
|
|
114
|
+
-x --x11_disable [x11_disable] Disables X11 forwarding.
|
|
115
|
+
-Y --x11_trusted [x11_trusted] Enables trusted X11 forwarding.
|
|
116
|
+
-y --log [log] Send log information using the syslog(3)
|
|
117
|
+
-H --ngrok [ngrok] Obtain connectiion hostport from ngrok
|
|
118
|
+
-J --keychain_service [keychain_service] Service name to obtain private key from system
|
|
119
|
+
keychain.
|
|
120
|
+
-j --keychain_account [keychain_account] Account name to obtain private key from system
|
|
121
|
+
keychain.
|
|
122
|
+
-h, --help display help for command
|
|
123
|
+
```
|
|
43
124
|
### Api examples
|
|
44
125
|
|
|
45
126
|
Exec remote commands using async await and processing result through streams.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
/// <reference types="node" />
|
|
5
|
+
import { Server, Socket } from 'net';
|
|
6
|
+
import { Writable } from 'stream';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import { Client, Channel } from 'electron-ssh2';
|
|
9
|
+
import { KeypairStorage } from './keypair_storage';
|
|
10
|
+
import { NgrokApi } from './ngrok_service';
|
|
11
|
+
import { main } from './ssh2-node';
|
|
12
|
+
interface Listener {
|
|
13
|
+
[key: string]: Server;
|
|
14
|
+
}
|
|
15
|
+
export type SSHConfig = {
|
|
16
|
+
hostname: string;
|
|
17
|
+
username: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
host?: string;
|
|
20
|
+
port?: string;
|
|
21
|
+
proxy_ports?: Array<string>;
|
|
22
|
+
remote_ports?: Array<string>;
|
|
23
|
+
private_key?: string | null;
|
|
24
|
+
private_key_filename?: string | null;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
server_name?: string;
|
|
27
|
+
service_name?: string;
|
|
28
|
+
compress?: string;
|
|
29
|
+
cypher?: string;
|
|
30
|
+
exec?: Array<string>;
|
|
31
|
+
shell?: boolean;
|
|
32
|
+
ngrok_api?: string;
|
|
33
|
+
keepaliveInterval?: number;
|
|
34
|
+
};
|
|
35
|
+
export declare class SSHTunnelProxy extends EventEmitter {
|
|
36
|
+
protected client: Client;
|
|
37
|
+
protected keypairStorage: KeypairStorage;
|
|
38
|
+
protected config: SSHConfig;
|
|
39
|
+
protected listener: Listener;
|
|
40
|
+
protected retries: number;
|
|
41
|
+
protected networkOnline: boolean;
|
|
42
|
+
protected _tunnelReadyTimeout: ReturnType<typeof setTimeout>;
|
|
43
|
+
debug_en: boolean;
|
|
44
|
+
debug_ssh: boolean;
|
|
45
|
+
constructor();
|
|
46
|
+
protected close_sockets(proxy_ports: Array<string>): void;
|
|
47
|
+
protected setup_ssh_forward(socket: Socket, remote_hostname: string, remote_port: string): void;
|
|
48
|
+
setupProxyPorts(proxy_ports: Array<string>): Promise<void>;
|
|
49
|
+
execCmd(cmd: string, dataStream?: Writable, errStream?: Writable): Promise<void>;
|
|
50
|
+
protected remote_shell(err: Error, stream: Channel): void;
|
|
51
|
+
protected cleanup_events(client: Client): void;
|
|
52
|
+
do_ssh_connect(opts: SSHConfig): Promise<void>;
|
|
53
|
+
validate_port_number(port: number, whitelist: object): boolean;
|
|
54
|
+
validate_local_forward(proxy_ports: Array<string>, whitelist: object): boolean;
|
|
55
|
+
protected ssh_retry_connect(opts: SSHConfig): void;
|
|
56
|
+
protected ssh_start_tunnel(opts: SSHConfig): Promise<unknown>;
|
|
57
|
+
connectSSH(opts: SSHConfig, whitelist: object | null): Promise<void>;
|
|
58
|
+
onNetworkOnline(): void;
|
|
59
|
+
onNetworkOffline(): void;
|
|
60
|
+
generateAndStoreKeypair(serviceName: string, account: string): void;
|
|
61
|
+
getPublicKey(serviceName: string, account: string): void;
|
|
62
|
+
getClient(): Client;
|
|
63
|
+
protected debug(...args: any[]): void;
|
|
64
|
+
}
|
|
65
|
+
export { KeypairStorage, NgrokApi, main as sshcli };
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
28
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
+
function step(op) {
|
|
31
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
33
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
34
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
+
switch (op[0]) {
|
|
36
|
+
case 0: case 1: t = op; break;
|
|
37
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
+
default:
|
|
41
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
+
if (t[2]) _.ops.pop();
|
|
46
|
+
_.trys.pop(); continue;
|
|
47
|
+
}
|
|
48
|
+
op = body.call(thisArg, _);
|
|
49
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
54
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
55
|
+
if (ar || !(i in from)) {
|
|
56
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
57
|
+
ar[i] = from[i];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
61
|
+
};
|
|
62
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
|
+
exports.sshcli = exports.NgrokApi = exports.KeypairStorage = exports.SSHTunnelProxy = void 0;
|
|
64
|
+
var net_1 = require("net");
|
|
65
|
+
var fs_1 = require("fs");
|
|
66
|
+
var process_1 = require("process");
|
|
67
|
+
var events_1 = require("events");
|
|
68
|
+
var electron_ssh2_1 = require("electron-ssh2");
|
|
69
|
+
var keypair_storage_1 = require("./keypair_storage");
|
|
70
|
+
Object.defineProperty(exports, "KeypairStorage", { enumerable: true, get: function () { return keypair_storage_1.KeypairStorage; } });
|
|
71
|
+
var ngrok_service_1 = require("./ngrok_service");
|
|
72
|
+
Object.defineProperty(exports, "NgrokApi", { enumerable: true, get: function () { return ngrok_service_1.NgrokApi; } });
|
|
73
|
+
var ssh2_node_1 = require("./ssh2-node");
|
|
74
|
+
Object.defineProperty(exports, "sshcli", { enumerable: true, get: function () { return ssh2_node_1.main; } });
|
|
75
|
+
var os_1 = require("os");
|
|
76
|
+
var SSHTunnelProxy = (function (_super) {
|
|
77
|
+
__extends(SSHTunnelProxy, _super);
|
|
78
|
+
function SSHTunnelProxy() {
|
|
79
|
+
var _this = _super.call(this) || this;
|
|
80
|
+
_this.listener = {};
|
|
81
|
+
_this.retries = 0;
|
|
82
|
+
_this.debug_en = false;
|
|
83
|
+
_this.debug_ssh = false;
|
|
84
|
+
_this._tunnelReadyTimeout = undefined;
|
|
85
|
+
_this.networkOnline = false;
|
|
86
|
+
_this.config = null;
|
|
87
|
+
_this.client = new electron_ssh2_1.Client();
|
|
88
|
+
_this.keypairStorage = new keypair_storage_1.KeypairStorage();
|
|
89
|
+
return _this;
|
|
90
|
+
}
|
|
91
|
+
SSHTunnelProxy.prototype.close_sockets = function (proxy_ports) {
|
|
92
|
+
var _this = this;
|
|
93
|
+
if (!proxy_ports)
|
|
94
|
+
return;
|
|
95
|
+
proxy_ports.forEach(function (proxy_port) {
|
|
96
|
+
var server = _this.listener[proxy_port];
|
|
97
|
+
if (server && server.listening) {
|
|
98
|
+
_this.debug_en && _this.debug('SSH Server :: closing forward:', proxy_port);
|
|
99
|
+
try {
|
|
100
|
+
server.close();
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
_this.debug_en && _this.debug('error closing server:', err);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
delete _this.listener[proxy_port];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
SSHTunnelProxy.prototype.setup_ssh_forward = function (socket, remote_hostname, remote_port) {
|
|
112
|
+
var _this = this;
|
|
113
|
+
this.client.forwardOut(socket.remoteAddress, socket.remotePort, remote_hostname, remote_port, function (err, stream) {
|
|
114
|
+
if (err) {
|
|
115
|
+
_this.debug_en && _this.debug('socket forward error:', err);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
stream.on('end', function () {
|
|
119
|
+
socket.resume();
|
|
120
|
+
});
|
|
121
|
+
stream.pipe(socket).pipe(stream);
|
|
122
|
+
var shutdown_forward;
|
|
123
|
+
try {
|
|
124
|
+
shutdown_forward = function () {
|
|
125
|
+
stream.unpipe(socket);
|
|
126
|
+
socket.unpipe(stream);
|
|
127
|
+
stream.end();
|
|
128
|
+
};
|
|
129
|
+
socket.on('close', function () {
|
|
130
|
+
shutdown_forward();
|
|
131
|
+
});
|
|
132
|
+
socket.on('error', function (err) {
|
|
133
|
+
_this.debug_en && _this.debug('socket on error:', err);
|
|
134
|
+
shutdown_forward();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
_this.debug_en && _this.debug('listen err:', err);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
SSHTunnelProxy.prototype.setupProxyPorts = function (proxy_ports) {
|
|
143
|
+
var _this = this;
|
|
144
|
+
return new Promise(function (resolve, reject) {
|
|
145
|
+
if (!proxy_ports) {
|
|
146
|
+
resolve();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
var listeners = 0;
|
|
150
|
+
proxy_ports.forEach(function (proxy_port) {
|
|
151
|
+
var _a = proxy_port.split(':'), local_port_str = _a[0], remote_hostname = _a[1], remote_port = _a[2];
|
|
152
|
+
var local_port = parseInt(local_port_str);
|
|
153
|
+
var server = _this.listener[proxy_port];
|
|
154
|
+
if (server && server.listening) {
|
|
155
|
+
_this.debug_en && _this.debug('SSH Server :: closing forward 2:', proxy_port);
|
|
156
|
+
server.close();
|
|
157
|
+
}
|
|
158
|
+
server = (0, net_1.createServer)({ allowHalfOpen: false }, function (socket) {
|
|
159
|
+
if (_this.debug_en) {
|
|
160
|
+
var debug_msg = 'SSH Server :: connection on ' + local_port + ' ' + socket.remotePort;
|
|
161
|
+
_this.debug(debug_msg);
|
|
162
|
+
}
|
|
163
|
+
_this.setup_ssh_forward(socket, remote_hostname, remote_port);
|
|
164
|
+
});
|
|
165
|
+
_this.listener[proxy_port] = server;
|
|
166
|
+
server.on('error', function (err) {
|
|
167
|
+
_this.debug_en && _this.debug('listen err:', err);
|
|
168
|
+
_this.emit('error', err);
|
|
169
|
+
reject(err);
|
|
170
|
+
});
|
|
171
|
+
server.listen(local_port, function () {
|
|
172
|
+
var status_msg = 'SSH Server :: bound on ' + local_port;
|
|
173
|
+
_this.debug_en && _this.debug(status_msg);
|
|
174
|
+
if (listeners++ >= proxy_ports.length - 1) {
|
|
175
|
+
_this.emit('ssh_tunnel_ready', {});
|
|
176
|
+
if (_this._tunnelReadyTimeout) {
|
|
177
|
+
clearTimeout(_this._tunnelReadyTimeout);
|
|
178
|
+
_this._tunnelReadyTimeout = undefined;
|
|
179
|
+
}
|
|
180
|
+
resolve();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
SSHTunnelProxy.prototype.execCmd = function (cmd, dataStream, errStream) {
|
|
187
|
+
var _this = this;
|
|
188
|
+
return new Promise(function (resolve, reject) {
|
|
189
|
+
_this.client.exec(cmd, function (err, stream) {
|
|
190
|
+
if (err) {
|
|
191
|
+
return reject(err);
|
|
192
|
+
}
|
|
193
|
+
stream.on('close', function () { return resolve(); });
|
|
194
|
+
if (dataStream) {
|
|
195
|
+
stream.on('data', function (data) { return dataStream.write(data); });
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
stream.on('data', function (data) { return process_1.stdout.write(data); });
|
|
199
|
+
}
|
|
200
|
+
if (errStream) {
|
|
201
|
+
stream.stderr.on('data', function (data) { return errStream.write(data); });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
stream.stderr.on('data', function (data) { return process_1.stderr.write(data); });
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
SSHTunnelProxy.prototype.remote_shell = function (err, stream) {
|
|
210
|
+
var _this = this;
|
|
211
|
+
if (err)
|
|
212
|
+
throw err;
|
|
213
|
+
process_1.stdin.setRawMode(true);
|
|
214
|
+
process_1.stdin.on('data', function (data) {
|
|
215
|
+
stream.stdin.write(data);
|
|
216
|
+
});
|
|
217
|
+
stream.stdout.on('data', function (data) {
|
|
218
|
+
process_1.stdout.write(data);
|
|
219
|
+
});
|
|
220
|
+
stream.on('close', function () {
|
|
221
|
+
process_1.stdin.setRawMode(false);
|
|
222
|
+
process_1.stdin.removeAllListeners();
|
|
223
|
+
stream.stdout.removeAllListeners();
|
|
224
|
+
(0, process_1.exit)();
|
|
225
|
+
}).stderr.on('data', function (data) {
|
|
226
|
+
_this.debug_en && _this.debug('shell' + data);
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
SSHTunnelProxy.prototype.cleanup_events = function (client) {
|
|
230
|
+
client.removeAllListeners('ssh_event');
|
|
231
|
+
};
|
|
232
|
+
SSHTunnelProxy.prototype.do_ssh_connect = function (opts) {
|
|
233
|
+
var _this = this;
|
|
234
|
+
var _client = this.client;
|
|
235
|
+
return new Promise(function (resolve) {
|
|
236
|
+
_this.close_sockets(opts.proxy_ports);
|
|
237
|
+
_this.cleanup_events(_client);
|
|
238
|
+
_client.on('ready', function () { return __awaiter(_this, void 0, void 0, function () {
|
|
239
|
+
var i, cmd;
|
|
240
|
+
return __generator(this, function (_a) {
|
|
241
|
+
switch (_a.label) {
|
|
242
|
+
case 0:
|
|
243
|
+
if (this._tunnelReadyTimeout) {
|
|
244
|
+
clearTimeout(this._tunnelReadyTimeout);
|
|
245
|
+
delete this._tunnelReadyTimeout;
|
|
246
|
+
}
|
|
247
|
+
if (!opts.proxy_ports) return [3, 2];
|
|
248
|
+
return [4, this.setupProxyPorts(opts.proxy_ports)];
|
|
249
|
+
case 1:
|
|
250
|
+
_a.sent();
|
|
251
|
+
_a.label = 2;
|
|
252
|
+
case 2:
|
|
253
|
+
if (!opts.shell) return [3, 3];
|
|
254
|
+
this.client.shell(this.remote_shell);
|
|
255
|
+
return [3, 7];
|
|
256
|
+
case 3:
|
|
257
|
+
if (!(opts.exec && opts.exec.length > 0)) return [3, 7];
|
|
258
|
+
i = 0;
|
|
259
|
+
_a.label = 4;
|
|
260
|
+
case 4:
|
|
261
|
+
if (!(i < opts.exec.length)) return [3, 7];
|
|
262
|
+
cmd = opts.exec[i];
|
|
263
|
+
this.debug_en && this.debug(opts.username + '@' + opts.hostname + ':' + cmd);
|
|
264
|
+
return [4, this.execCmd(cmd)];
|
|
265
|
+
case 5:
|
|
266
|
+
_a.sent();
|
|
267
|
+
_a.label = 6;
|
|
268
|
+
case 6:
|
|
269
|
+
i++;
|
|
270
|
+
return [3, 4];
|
|
271
|
+
case 7:
|
|
272
|
+
resolve();
|
|
273
|
+
return [2];
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}); });
|
|
277
|
+
_client.on('end', function () {
|
|
278
|
+
_this.debug_en && _this.debug('SSH Client :: end');
|
|
279
|
+
_this.close_sockets(opts.proxy_ports);
|
|
280
|
+
});
|
|
281
|
+
_client.on('close', function () {
|
|
282
|
+
_this.debug_en && _this.debug('SSH Client :: close');
|
|
283
|
+
_this.close_sockets(opts.proxy_ports);
|
|
284
|
+
});
|
|
285
|
+
_client.on('error', function (err) {
|
|
286
|
+
_this.debug_en && _this.debug('SSH Client :: error :: ' + err);
|
|
287
|
+
if (_this._tunnelReadyTimeout) {
|
|
288
|
+
clearTimeout(_this._tunnelReadyTimeout);
|
|
289
|
+
_this._tunnelReadyTimeout = undefined;
|
|
290
|
+
}
|
|
291
|
+
_this.ssh_retry_connect(opts);
|
|
292
|
+
resolve();
|
|
293
|
+
});
|
|
294
|
+
var config = JSON.parse(JSON.stringify(opts));
|
|
295
|
+
config.debug = _this.debug_ssh
|
|
296
|
+
? function () {
|
|
297
|
+
var args = [];
|
|
298
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
299
|
+
args[_i] = arguments[_i];
|
|
300
|
+
}
|
|
301
|
+
console.log.apply(console, args);
|
|
302
|
+
}
|
|
303
|
+
: null;
|
|
304
|
+
config.keepaliveInterval = opts.keepaliveInterval || 10000;
|
|
305
|
+
var ssh_tunnel_extensions = [
|
|
306
|
+
'alias',
|
|
307
|
+
'disabled',
|
|
308
|
+
'hostname',
|
|
309
|
+
'ngrok_api',
|
|
310
|
+
'server_name',
|
|
311
|
+
'service_name',
|
|
312
|
+
'whitelist',
|
|
313
|
+
'private_key_filename',
|
|
314
|
+
];
|
|
315
|
+
for (var i = 0; i < ssh_tunnel_extensions.length; i++)
|
|
316
|
+
delete config[ssh_tunnel_extensions[i]];
|
|
317
|
+
_client.connect(config);
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
SSHTunnelProxy.prototype.validate_port_number = function (port, whitelist) {
|
|
321
|
+
if (isNaN(port) || port < 1 || port > 65535 || (port < 1024 && whitelist && whitelist[port] === undefined))
|
|
322
|
+
return false;
|
|
323
|
+
return true;
|
|
324
|
+
};
|
|
325
|
+
SSHTunnelProxy.prototype.validate_local_forward = function (proxy_ports, whitelist) {
|
|
326
|
+
var _this = this;
|
|
327
|
+
if (!proxy_ports)
|
|
328
|
+
return true;
|
|
329
|
+
var invalid_ports = [];
|
|
330
|
+
proxy_ports.forEach(function (proxy_port) {
|
|
331
|
+
var _a = proxy_port.split(':'), local_port = _a[0], remote_hostname = _a[1], remote_port = _a[2];
|
|
332
|
+
if (remote_hostname.length < 1)
|
|
333
|
+
invalid_ports.push({ port: remote_port, type: 'remote' });
|
|
334
|
+
if (!_this.validate_port_number(parseInt(local_port), whitelist))
|
|
335
|
+
invalid_ports.push({ port: local_port, type: 'local' });
|
|
336
|
+
if (!_this.validate_port_number(parseInt(remote_port), whitelist))
|
|
337
|
+
invalid_ports.push({ port: remote_port, type: 'remote' });
|
|
338
|
+
});
|
|
339
|
+
if (invalid_ports.length) {
|
|
340
|
+
var err = new Error('Invalid local forward');
|
|
341
|
+
this.debug_en &&
|
|
342
|
+
this.debug('invalid ports found:\n', JSON.stringify(proxy_ports, null, 2), '\n', JSON.stringify(whitelist, null, 2));
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
};
|
|
347
|
+
SSHTunnelProxy.prototype.ssh_retry_connect = function (opts) {
|
|
348
|
+
var _this = this;
|
|
349
|
+
var invoke = function () {
|
|
350
|
+
_this.connectSSH(opts, null);
|
|
351
|
+
};
|
|
352
|
+
if (this.retries++ < 10) {
|
|
353
|
+
this._tunnelReadyTimeout = setTimeout(invoke, 5000);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
this.debug_en && this.debug("Retry attempts exhausted.");
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
SSHTunnelProxy.prototype.ssh_start_tunnel = function (opts) {
|
|
360
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
361
|
+
var do_ssh_connect;
|
|
362
|
+
var _this = this;
|
|
363
|
+
return __generator(this, function (_a) {
|
|
364
|
+
do_ssh_connect = function (resolve) { return __awaiter(_this, void 0, void 0, function () {
|
|
365
|
+
var _this = this;
|
|
366
|
+
return __generator(this, function (_a) {
|
|
367
|
+
switch (_a.label) {
|
|
368
|
+
case 0: return [4, this.do_ssh_connect(opts).then(function () {
|
|
369
|
+
var hostport = opts.host + ':' + opts.port;
|
|
370
|
+
if (_this.debug_en) {
|
|
371
|
+
var msg = 'SSH connection to ' + opts.server_name + ' established at ' + hostport;
|
|
372
|
+
_this.debug(msg);
|
|
373
|
+
}
|
|
374
|
+
_this.emit('status', '', 'ready', hostport);
|
|
375
|
+
_this.retries = 0;
|
|
376
|
+
resolve();
|
|
377
|
+
})];
|
|
378
|
+
case 1:
|
|
379
|
+
_a.sent();
|
|
380
|
+
return [2];
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}); };
|
|
384
|
+
return [2, new Promise(do_ssh_connect)];
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
SSHTunnelProxy.prototype.connectSSH = function (opts, whitelist) {
|
|
389
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
390
|
+
var _opts, _a, ngrokApi, hostport, err;
|
|
391
|
+
var _this = this;
|
|
392
|
+
return __generator(this, function (_b) {
|
|
393
|
+
switch (_b.label) {
|
|
394
|
+
case 0:
|
|
395
|
+
_opts = JSON.parse(JSON.stringify(opts));
|
|
396
|
+
_opts.whitelist = whitelist !== undefined ? whitelist : _opts.whitelist ? _opts.whitelist : null;
|
|
397
|
+
try {
|
|
398
|
+
this.validate_local_forward(_opts.proxy_ports, _opts.whitelist);
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
this.debug_en && this.debug(err);
|
|
402
|
+
throw (err);
|
|
403
|
+
}
|
|
404
|
+
if (opts.password && !opts.password.length) {
|
|
405
|
+
_opts.password = undefined;
|
|
406
|
+
}
|
|
407
|
+
if (_opts.private_key_filename) {
|
|
408
|
+
try {
|
|
409
|
+
if (_opts.private_key_filename[0] == '~') {
|
|
410
|
+
_opts.private_key_filename = os_1.homedir + _opts.private_key_filename.substr(1);
|
|
411
|
+
}
|
|
412
|
+
_opts.privateKey = (0, fs_1.readFileSync)(_opts.private_key_filename).toString();
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
this.debug_en && this.debug(err);
|
|
416
|
+
throw (err);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (!(_opts.service_name && _opts.server_name)) return [3, 2];
|
|
420
|
+
_a = _opts;
|
|
421
|
+
return [4, this.keypairStorage.get_keypair(opts.service_name, opts.server_name)];
|
|
422
|
+
case 1:
|
|
423
|
+
_a.privateKey = _b.sent();
|
|
424
|
+
return [3, 3];
|
|
425
|
+
case 2:
|
|
426
|
+
if (!_opts.server_name)
|
|
427
|
+
_opts.server_name = 'sshtun';
|
|
428
|
+
_b.label = 3;
|
|
429
|
+
case 3:
|
|
430
|
+
if (!_opts.ngrok_api) return [3, 5];
|
|
431
|
+
ngrokApi = new ngrok_service_1.NgrokApi(_opts.ngrok_api);
|
|
432
|
+
return [4, ngrokApi.get_hostport().catch(function () {
|
|
433
|
+
_this.debug_en && _this.debug('ngrok get_hostport connection error');
|
|
434
|
+
_this.ssh_retry_connect(_opts);
|
|
435
|
+
})];
|
|
436
|
+
case 4:
|
|
437
|
+
hostport = _b.sent();
|
|
438
|
+
if (hostport && hostport.host) {
|
|
439
|
+
_opts.host = hostport.host;
|
|
440
|
+
_opts.port = hostport.port;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
err = new Error('invalid hostport obtained from ngrok api');
|
|
444
|
+
this.debug_en && this.debug(err);
|
|
445
|
+
throw (err);
|
|
446
|
+
}
|
|
447
|
+
_b.label = 5;
|
|
448
|
+
case 5:
|
|
449
|
+
this.debug_en && this.debug('connectSSH.ts:\n', JSON.stringify(_opts, null, 2));
|
|
450
|
+
return [4, this.ssh_start_tunnel(_opts).catch(function (err) {
|
|
451
|
+
_this.debug_en && _this.debug('ssh_start_tunnel catch:' + err);
|
|
452
|
+
})];
|
|
453
|
+
case 6:
|
|
454
|
+
_b.sent();
|
|
455
|
+
return [2];
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
SSHTunnelProxy.prototype.onNetworkOnline = function () {
|
|
461
|
+
this.networkOnline = true;
|
|
462
|
+
};
|
|
463
|
+
SSHTunnelProxy.prototype.onNetworkOffline = function () {
|
|
464
|
+
this.networkOnline = false;
|
|
465
|
+
};
|
|
466
|
+
SSHTunnelProxy.prototype.generateAndStoreKeypair = function (serviceName, account) {
|
|
467
|
+
this.keypairStorage.generate_and_store_keypair(serviceName, account);
|
|
468
|
+
};
|
|
469
|
+
SSHTunnelProxy.prototype.getPublicKey = function (serviceName, account) {
|
|
470
|
+
this.keypairStorage.get_public_key_from_keychain(serviceName, account);
|
|
471
|
+
};
|
|
472
|
+
SSHTunnelProxy.prototype.getClient = function () {
|
|
473
|
+
return this.client;
|
|
474
|
+
};
|
|
475
|
+
SSHTunnelProxy.prototype.debug = function () {
|
|
476
|
+
var args = [];
|
|
477
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
478
|
+
args[_i] = arguments[_i];
|
|
479
|
+
}
|
|
480
|
+
console.log.apply(console, args);
|
|
481
|
+
this.emit.apply(this, __spreadArray(['debug'], args, false));
|
|
482
|
+
};
|
|
483
|
+
return SSHTunnelProxy;
|
|
484
|
+
}(events_1.EventEmitter));
|
|
485
|
+
exports.SSHTunnelProxy = SSHTunnelProxy;
|
|
486
|
+
//# sourceMappingURL=index.js.map
|