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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user