forked from mfgea/tunnel-exec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
148 lines (118 loc) · 3.84 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
var childProcess = require("child_process");
var portfinder = require('portfinder');
var assert = require("assert");
/**
* Merges values from both obj1 and obj2, overwriting obj1's values and adding obj2's if non existent in obj1
* @param obj1
* @param obj2
* @returns obj3 a new object based on obj1 and obj2
*/
function merge_options(obj1,obj2){
var obj3 = {};
for (var attr in obj1) { obj3[attr] = obj1[attr]; }
for (var attr in obj2) { obj3[attr] = obj2[attr]; }
return obj3;
}
/**
* Connect to the remote SSH host, then open a tunnel to the Target Host.
* After that, executes a given callback and then closes the tunnel.
*/
function tunnelExec(options, callback) {
var defaultOptions = {
user: null,
identityFile: null,
localPort: null,
// Host which is being SSH'd
remoteHost: null,
remotePort: 22,
// Jump hosts support. Hops will be used in the order of the array
jumpHosts: [],
// Target host, will receive the commands executed through the tunnel
targetHost: null,
targetPort: null,
// Enable compression
compression: false,
// Connection timeout
timeout: 15000
};
var params = merge_options(defaultOptions, options);
if(!params.remoteHost){
callback(new Error('Missing remoteHost'));
return false;
}
if(!params.targetPort){
callback(new Error('missing targetPort'));
return false;
}
// If no local port is set, we find one through portfinder
if (!params.localPort) {
return portfinder.getPort(function(err, port) {
params.localPort = port;
tunnelExec(params, cb);
});
}
// Builds remote host string
var connectHost = params.remoteHost;
if(params.user){
connectHost = params.user + "@" + connectHost;
}
// If no target Host, assume is the same as the remoteHost
if(!params.targetHost){
params.targetHost = params.remoteHost;
}
// Sets arguments for the ssh command
var args = [
"-p",
params.remotePort,
connectHost,
"-L",
params.localPort + ":" + params.targetHost + ':' + params.targetPort,
"-N",
"-v"
];
// Adds identityFile if any exists
if(params.identityFile){
args.push('-i');
args.push(params.identityFile);
}
// Jumps hosts support
if (params.jumpHosts) {
const hops = [];
params.jumpHosts.forEach((hop) => {
const { user = params.user || '', host, port = params.remotePort } = hop;
hops.push(`${user && `${user}@`}${host}:${port}`);
});
// If we have at least one jump host, we amend the CLI arguments to include -J hop1,hop2,hop3,...
if (hops.length > 0) {
args.push('-J');
args.push(hops.join(','));
}
}
// Compression support
if (params.compression) {
args.push('-C');
}
// Force native (english) language, so we can read debug messages correctly
process.env['LANG'] = 'C';
var child = childProcess.spawn("ssh", args);
var timeoutKillProcess = setTimeout(kill, params.timeout);
function kill() {
child.kill('SIGKILL');
callback(new Error('Error establishing SSH connection'));
}
function close() {
child.kill('SIGKILL');
}
// When the process receives data, process it and start callback (if forwarding succeded)
child.stderr.on('data', function bootload(data) {
if (data.toString().match(/local forwarding listening/i)) {
clearTimeout(timeoutKillProcess);
child.stderr.removeListener('data', bootload);
callback(null, {
close: close,
params: params
});
}
});
}
module.exports = tunnelExec;