v1.3.0: add inverse ssh proxy
This commit is contained in:
parent
eba2b4e5b2
commit
0361e5762d
15
README.md
15
README.md
|
@ -55,8 +55,9 @@ sclient [flags] <remote> <local>
|
||||||
```
|
```
|
||||||
|
|
||||||
* flags
|
* flags
|
||||||
* -k, --insecure ignore invalid TLS (SSL/HTTPS) certificates
|
* `-k, --insecure` ignore invalid TLS (SSL/HTTPS) certificates
|
||||||
* --servername <string> spoof SNI (to disable use IP as <remote> and do not use this option)
|
* `--servername <string>` spoof SNI (to disable use IP as <remote> and do not use this option)
|
||||||
|
* `--ssh proxy` proxy ssh over https (_inverse_ ssh proxy, like stunnel)
|
||||||
* remote
|
* remote
|
||||||
* must have servername (i.e. example.com)
|
* must have servername (i.e. example.com)
|
||||||
* port is optional (default is 443)
|
* port is optional (default is 443)
|
||||||
|
@ -85,7 +86,7 @@ Ignore a bad TLS/SSL/HTTPS certificate and connect anyway.
|
||||||
sclient -k badtls.telebit.cloud:443 localhost:3000
|
sclient -k badtls.telebit.cloud:443 localhost:3000
|
||||||
```
|
```
|
||||||
|
|
||||||
Reading from stdin
|
### Reading from stdin
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sclient telebit.cloud:443 -
|
sclient telebit.cloud:443 -
|
||||||
|
@ -95,7 +96,13 @@ sclient telebit.cloud:443 -
|
||||||
sclient telebit.cloud:443 - </path/to/file
|
sclient telebit.cloud:443 - </path/to/file
|
||||||
```
|
```
|
||||||
|
|
||||||
Piping
|
### ssh over https
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sclient --ssh user@telebit.cloud
|
||||||
|
```
|
||||||
|
|
||||||
|
### Piping
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
printf "GET / HTTP/1.1\r\nHost: telebit.cloud\r\n\r\n" | sclient telebit.cloud:443
|
printf "GET / HTTP/1.1\r\nHost: telebit.cloud\r\n\r\n" | sclient telebit.cloud:443
|
||||||
|
|
|
@ -8,6 +8,7 @@ var localAddress;
|
||||||
var localPort;
|
var localPort;
|
||||||
var rejectUnauthorized;
|
var rejectUnauthorized;
|
||||||
var servername;
|
var servername;
|
||||||
|
var sshProxy;
|
||||||
|
|
||||||
function usage() {
|
function usage() {
|
||||||
console.info("");
|
console.info("");
|
||||||
|
@ -38,6 +39,14 @@ function parseFlags() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
process.argv.some(function (arg, i) {
|
||||||
|
if (/^--?ssh$/.test(arg)) {
|
||||||
|
sshProxy = true;
|
||||||
|
process.argv.splice(i, 1);
|
||||||
|
process.argv[3] = '$';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
parseFlags();
|
parseFlags();
|
||||||
|
@ -45,6 +54,15 @@ parseFlags();
|
||||||
remote = (process.argv[2]||'').split(':');
|
remote = (process.argv[2]||'').split(':');
|
||||||
local = (process.argv[3]||'').split(':');
|
local = (process.argv[3]||'').split(':');
|
||||||
|
|
||||||
|
if ('ssh' === remote[0]) {
|
||||||
|
sshProxy = true;
|
||||||
|
remote = local;
|
||||||
|
local = ['$'];
|
||||||
|
} else if ('ssh' === local[0]) {
|
||||||
|
sshProxy = true;
|
||||||
|
local[0] = '$';
|
||||||
|
}
|
||||||
|
|
||||||
// arg 0 is node
|
// arg 0 is node
|
||||||
// arg 1 is sclient
|
// arg 1 is sclient
|
||||||
// arg 2 is remote
|
// arg 2 is remote
|
||||||
|
@ -72,23 +90,57 @@ if (local[0] === String(parseInt(local[0], 10))) {
|
||||||
localPort = parseInt(local[0], 10);
|
localPort = parseInt(local[0], 10);
|
||||||
localAddress = 'localhost';
|
localAddress = 'localhost';
|
||||||
} else {
|
} else {
|
||||||
localAddress = local[0]; // potentially '-' or '|'
|
localAddress = local[0]; // potentially '-' or '|' or '$'
|
||||||
localPort = parseInt(local[1], 10);
|
localPort = parseInt(local[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('-' === localAddress || '|' === localAddress) {
|
var rparts = remote[0].split('@');
|
||||||
// no need for port
|
var username = rparts[1] ? (rparts[0] + '@') : '';
|
||||||
} else if (!localPort) {
|
|
||||||
usage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts = {
|
var opts = {
|
||||||
remoteAddr: remote[0]
|
remoteAddr: rparts[1] || rparts[0]
|
||||||
, remotePort: remote[1] || 443
|
, remotePort: remote[1] || 443
|
||||||
, localAddress: localAddress
|
, localAddress: localAddress
|
||||||
, localPort: localPort
|
, localPort: localPort
|
||||||
, rejectUnauthorized: rejectUnauthorized
|
, rejectUnauthorized: rejectUnauthorized
|
||||||
, servername: servername
|
, servername: servername
|
||||||
|
, stdin: null
|
||||||
|
, stdout: null
|
||||||
};
|
};
|
||||||
require('../')(opts);
|
|
||||||
|
if ('-' === localAddress || '|' === localAddress) {
|
||||||
|
opts.stdin = process.stdin;
|
||||||
|
opts.stdout = process.stdout;
|
||||||
|
// no need for port
|
||||||
|
} else if ('$' === localAddress) {
|
||||||
|
sshProxy = true;
|
||||||
|
opts.localAddress = 'localhost';
|
||||||
|
opts.localPort = 0; // choose at random
|
||||||
|
} else if (!localPort) {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var emitter = require('../')(opts);
|
||||||
|
emitter.once('listening', function (opts) {
|
||||||
|
var port = opts.localPort;
|
||||||
|
console.info('[listening] ' + opts.remoteAddr + ":" + opts.remotePort
|
||||||
|
+ " <= " + opts.localAddress + ":" + opts.localPort);
|
||||||
|
|
||||||
|
if (sshProxy) {
|
||||||
|
// TODO choose at random and connect to ssh after test
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
var ssh = spawn('ssh', [
|
||||||
|
username + 'localhost'
|
||||||
|
, '-p', port
|
||||||
|
// we're _inverse_ proxying ssh, so we must alias the serveranem and ignore the IP
|
||||||
|
, '-o', 'HostKeyAlias=' + opts.remoteAddr
|
||||||
|
, '-o', 'CheckHostIP=no'
|
||||||
|
], { stdio: 'inherit' });
|
||||||
|
ssh.on('exit', function () {
|
||||||
|
console.info('shutting down...');
|
||||||
|
});
|
||||||
|
ssh.on('close', function () {
|
||||||
|
opts.server.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
24
index.js
24
index.js
|
@ -3,7 +3,7 @@
|
||||||
var net = require('net');
|
var net = require('net');
|
||||||
var tls = require('tls');
|
var tls = require('tls');
|
||||||
|
|
||||||
function listenForConns(opts) {
|
function listenForConns(emitter, opts) {
|
||||||
function pipeConn(c, out) {
|
function pipeConn(c, out) {
|
||||||
var sclient = tls.connect({
|
var sclient = tls.connect({
|
||||||
servername: opts.remoteAddr, host: opts.remoteAddr, port: opts.remotePort
|
servername: opts.remoteAddr, host: opts.remoteAddr, port: opts.remotePort
|
||||||
|
@ -28,7 +28,7 @@ function listenForConns(opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('-' === opts.localAddress || '|' === opts.localAddress) {
|
if ('-' === opts.localAddress || '|' === opts.localAddress) {
|
||||||
pipeConn(process.stdin, process.stdout);
|
pipeConn(opts.stdin, opts.stdout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +40,15 @@ function listenForConns(opts) {
|
||||||
host: opts.localAddress
|
host: opts.localAddress
|
||||||
, port: opts.localPort
|
, port: opts.localPort
|
||||||
}, function () {
|
}, function () {
|
||||||
console.info('[listening] ' + opts.remoteAddr + ":" + opts.remotePort
|
opts.localPort = this.address().port;
|
||||||
+ " <= " + opts.localAddress + ":" + opts.localPort);
|
opts.server = this;
|
||||||
|
emitter.emit('listening', opts);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function testConn(opts) {
|
function testConn(opts) {
|
||||||
|
var emitter = new (require('events').EventEmitter)();
|
||||||
|
|
||||||
// Test connection first
|
// Test connection first
|
||||||
var tlsOpts = {
|
var tlsOpts = {
|
||||||
host: opts.remoteAddr, port: opts.remotePort
|
host: opts.remoteAddr, port: opts.remotePort
|
||||||
|
@ -53,15 +56,24 @@ function testConn(opts) {
|
||||||
};
|
};
|
||||||
if (opts.servername) {
|
if (opts.servername) {
|
||||||
tlsOpts.servername = opts.servername;
|
tlsOpts.servername = opts.servername;
|
||||||
|
} else if (/^[\w\.\-]+\.[a-z]{2,}$/i.test(opts.remoteAddr)) {
|
||||||
|
tlsOpts.servername = opts.remoteAddr.toLowerCase();
|
||||||
|
}
|
||||||
|
if (opts.alpn) {
|
||||||
|
tlsOpts.ALPNProtocols = [ 'http', 'h2' ];
|
||||||
}
|
}
|
||||||
var tlsSock = tls.connect(tlsOpts, function () {
|
var tlsSock = tls.connect(tlsOpts, function () {
|
||||||
|
setTimeout(function () {
|
||||||
tlsSock.end();
|
tlsSock.end();
|
||||||
listenForConns(opts);
|
listenForConns(emitter, opts);
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
tlsSock.on('error', function (err) {
|
tlsSock.on('error', function (err) {
|
||||||
console.warn("[warn] '" + opts.remoteAddr + ":" + opts.remotePort + "' may not be accepting connections: ", err.toString(), '\n');
|
console.warn("[warn] '" + opts.remoteAddr + ":" + opts.remotePort + "' may not be accepting connections: ", err.toString(), '\n');
|
||||||
listenForConns(opts);
|
listenForConns(emitter, opts);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return emitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = testConn;
|
module.exports = testConn;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "sclient",
|
"name": "sclient",
|
||||||
"version": "1.2.2",
|
"version": "1.3.0",
|
||||||
"description": "Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally. Also ideal for multiplexing a single port with multiple protocols using SNI.",
|
"description": "Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally. Also ideal for multiplexing a single port with multiple protocols using SNI.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"homepage": "https://telebit.cloud/sclient/",
|
"homepage": "https://telebit.cloud/sclient/",
|
||||||
|
|
Loading…
Reference in New Issue