implemented ability to proxy TLS based on SNI
This commit is contained in:
parent
f32db19b52
commit
99a3de6496
|
@ -20,6 +20,22 @@ module.exports.create = function (deps, config) {
|
|||
var tunnelAdminTlsOpts = {};
|
||||
var tls = require('tls');
|
||||
|
||||
function domainMatches(pattern, servername) {
|
||||
// Everything matches '*'
|
||||
if (pattern === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/^\*./.test(pattern)) {
|
||||
// get rid of the leading "*." to more easily check the servername against it
|
||||
pattern = pattern.slice(2);
|
||||
return pattern === servername.slice(-pattern.length);
|
||||
}
|
||||
|
||||
// pattern doesn't contains any wildcards, so exact match is required
|
||||
return pattern === servername;
|
||||
}
|
||||
|
||||
var tcpRouter = {
|
||||
_map: { }
|
||||
, _create: function (address, port) {
|
||||
|
@ -123,68 +139,103 @@ module.exports.create = function (deps, config) {
|
|||
}
|
||||
};
|
||||
var tlsRouter = {
|
||||
_map: { }
|
||||
, _create: function (address, port/*, nextServer*/) {
|
||||
// port provides hinting for https, smtps, etc
|
||||
return function (socket, firstChunk, opts) {
|
||||
if (opts.hyperPeek) {
|
||||
// See "PEEK COMMENT" for more info
|
||||
// This was peeked at properly, so we don't have to re-wrap it
|
||||
// in order to get the connection to not hang.
|
||||
// The real first 'data' and 'readable' events will occur as they should
|
||||
program.tlsTunnelServer.emit('connection', socket);
|
||||
return;
|
||||
proxy: function (socket, opts, mod) {
|
||||
var newConn = deps.net.createConnection({
|
||||
port: mod.port
|
||||
, host: mod.address || '127.0.0.1'
|
||||
|
||||
, servername: opts.servername
|
||||
, data: opts.data
|
||||
, remoteFamily: opts.family || socket.remoteFamily || socket._remoteFamily || socket._handle._parent.owner.stream.remoteFamily
|
||||
, remoteAddress: opts.address || socket.remoteAddress || socket._remoteAddress || socket._handle._parent.owner.stream.remoteAddress
|
||||
, remotePort: opts.port || socket.remotePort || socket._remotePort || socket._handle._parent.owner.stream.remotePort
|
||||
}, function () {
|
||||
// this will happen before 'data' is triggered
|
||||
});
|
||||
|
||||
newConn.pipe(socket);
|
||||
socket.pipe(newConn);
|
||||
}
|
||||
, terminate: function (socket) {
|
||||
// We terminate the TLS by emitting the connections of the TLS server and it should handle
|
||||
// everything we need to do for us.
|
||||
program.tlsTunnelServer.emit('connection', socket);
|
||||
}
|
||||
|
||||
, handleModules: function (socket, opts) {
|
||||
// needs to wind up in one of 2 states:
|
||||
// 1. SNI-based Proxy / Tunnel (we don't even need to put it through the tlsSocket)
|
||||
// 2. Terminated (goes on to a particular module or route, including the admin interface)
|
||||
|
||||
var handled = (config.tls.modules || []).some(function (mod) {
|
||||
var relevant = mod.domains.some(function (pattern) {
|
||||
return domainMatches(pattern, opts.servername);
|
||||
});
|
||||
if (!relevant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var servername = opts.servername;
|
||||
var packerStream = require('tunnel-packer').Stream;
|
||||
var myDuplex = packerStream.create(socket);
|
||||
if (mod.name === 'proxy') {
|
||||
tlsRouter.proxy(socket, opts, mod);
|
||||
}
|
||||
else if (mod.name === 'terminate') {
|
||||
tlsRouter.terminate(socket);
|
||||
}
|
||||
else {
|
||||
console.error('saw unknown TLS module', mod);
|
||||
return false;
|
||||
}
|
||||
|
||||
myDuplex.remoteAddress = opts.remoteAddress || myDuplex.remoteAddress;
|
||||
myDuplex.remotePort = opts.remotePort || myDuplex.remotePort;
|
||||
console.log('[tlsRouter] ' + address + ':' + port + ' servername', servername, myDuplex.remoteAddress);
|
||||
return true;
|
||||
});
|
||||
|
||||
// needs to wind up in one of 3 states:
|
||||
// 1. SNI-based Proxy / Tunnel (we don't even need to put it through the tlsSocket)
|
||||
// 2. Admin Interface (skips the proxying)
|
||||
// 3. Terminated (goes on to a particular module or route)
|
||||
//myDuplex.__tlsTerminated = true;
|
||||
|
||||
process.nextTick(function () {
|
||||
// this must happen after the socket is emitted to the next in the chain,
|
||||
// but before any more data comes in via the network
|
||||
socket.unshift(firstChunk);
|
||||
});
|
||||
|
||||
// nextServer.emit could be used here
|
||||
program.tlsTunnelServer.emit('connection', myDuplex);
|
||||
|
||||
// Why all this wacky-do with the myDuplex?
|
||||
// because https://github.com/nodejs/node/issues/8854, that's why
|
||||
// (because node's internal networking layer == 💩 sometimes)
|
||||
socket.on('data', function (chunk) {
|
||||
console.log('[' + Date.now() + '] tls socket data', chunk.byteLength);
|
||||
myDuplex.push(chunk);
|
||||
});
|
||||
socket.on('error', function (err) {
|
||||
console.error('[error] httpsTunnel (Admin) TODO close');
|
||||
console.error(err);
|
||||
myDuplex.emit('error', err);
|
||||
});
|
||||
socket.on('close', function () {
|
||||
myDuplex.end();
|
||||
});
|
||||
};
|
||||
// We gotta do something, so when in doubt terminate the TLS since we don't really have
|
||||
// any good place to default to when proxying.
|
||||
if (!handled) {
|
||||
tlsRouter.terminate(socket);
|
||||
}
|
||||
}
|
||||
, get: function getTcpRouter(address, port) {
|
||||
address = address || '0.0.0.0';
|
||||
|
||||
var id = address + ':' + port;
|
||||
if (!tlsRouter._map[id]) {
|
||||
tlsRouter._map[id] = tlsRouter._create(address, port);
|
||||
, processSocket: function (socket, firstChunk, opts) {
|
||||
if (opts.hyperPeek) {
|
||||
// See "PEEK COMMENT" for more info
|
||||
// This was already peeked at by the tunneler and this connection has been created
|
||||
// in a way that should work with node's TLS server, so we don't need to do any
|
||||
// of the myDuplex stuff that we need to do with non-tunnel connections.
|
||||
tlsRouter.handleModules(socket, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
return tlsRouter._map[id];
|
||||
// Why all this wacky-do with the myDuplex?
|
||||
// because https://github.com/nodejs/node/issues/8854, that's why
|
||||
// (because node's internal networking layer == 💩 sometimes)
|
||||
var myDuplex = require('tunnel-packer').Stream.create(socket);
|
||||
myDuplex.remoteAddress = opts.remoteAddress || myDuplex.remoteAddress;
|
||||
myDuplex.remotePort = opts.remotePort || myDuplex.remotePort;
|
||||
|
||||
socket.on('data', function (chunk) {
|
||||
console.log('[' + Date.now() + '] tls socket data', chunk.byteLength);
|
||||
myDuplex.push(chunk);
|
||||
});
|
||||
socket.on('error', function (err) {
|
||||
console.error('[error] httpsTunnel (Admin) TODO close');
|
||||
console.error(err);
|
||||
myDuplex.emit('error', err);
|
||||
});
|
||||
socket.on('close', function () {
|
||||
myDuplex.end();
|
||||
});
|
||||
|
||||
var address = opts.localAddress || socket.localAddress;
|
||||
var port = opts.localPort || socket.localPort;
|
||||
console.log('[tlsRouter] ' + address + ':' + port + ' servername', opts.servername, myDuplex.remoteAddress);
|
||||
|
||||
tlsRouter.handleModules(myDuplex, opts);
|
||||
process.nextTick(function () {
|
||||
// this must happen after the socket is emitted to the next in the chain,
|
||||
// but before any more data comes in via the network
|
||||
socket.unshift(firstChunk);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -197,7 +248,7 @@ module.exports.create = function (deps, config) {
|
|||
if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) {
|
||||
console.log('tryTls');
|
||||
opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid';
|
||||
tlsRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts);
|
||||
tlsRouter.processSocket(conn, firstChunk, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue