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 tunnelAdminTlsOpts = {};
|
||||||
var tls = require('tls');
|
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 = {
|
var tcpRouter = {
|
||||||
_map: { }
|
_map: { }
|
||||||
, _create: function (address, port) {
|
, _create: function (address, port) {
|
||||||
|
@ -123,68 +139,103 @@ module.exports.create = function (deps, config) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var tlsRouter = {
|
var tlsRouter = {
|
||||||
_map: { }
|
proxy: function (socket, opts, mod) {
|
||||||
, _create: function (address, port/*, nextServer*/) {
|
var newConn = deps.net.createConnection({
|
||||||
// port provides hinting for https, smtps, etc
|
port: mod.port
|
||||||
return function (socket, firstChunk, opts) {
|
, host: mod.address || '127.0.0.1'
|
||||||
if (opts.hyperPeek) {
|
|
||||||
// See "PEEK COMMENT" for more info
|
, servername: opts.servername
|
||||||
// This was peeked at properly, so we don't have to re-wrap it
|
, data: opts.data
|
||||||
// in order to get the connection to not hang.
|
, remoteFamily: opts.family || socket.remoteFamily || socket._remoteFamily || socket._handle._parent.owner.stream.remoteFamily
|
||||||
// The real first 'data' and 'readable' events will occur as they should
|
, remoteAddress: opts.address || socket.remoteAddress || socket._remoteAddress || socket._handle._parent.owner.stream.remoteAddress
|
||||||
program.tlsTunnelServer.emit('connection', socket);
|
, remotePort: opts.port || socket.remotePort || socket._remotePort || socket._handle._parent.owner.stream.remotePort
|
||||||
return;
|
}, 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;
|
if (mod.name === 'proxy') {
|
||||||
var packerStream = require('tunnel-packer').Stream;
|
tlsRouter.proxy(socket, opts, mod);
|
||||||
var myDuplex = packerStream.create(socket);
|
}
|
||||||
|
else if (mod.name === 'terminate') {
|
||||||
|
tlsRouter.terminate(socket);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('saw unknown TLS module', mod);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
myDuplex.remoteAddress = opts.remoteAddress || myDuplex.remoteAddress;
|
return true;
|
||||||
myDuplex.remotePort = opts.remotePort || myDuplex.remotePort;
|
});
|
||||||
console.log('[tlsRouter] ' + address + ':' + port + ' servername', servername, myDuplex.remoteAddress);
|
|
||||||
|
|
||||||
// needs to wind up in one of 3 states:
|
// We gotta do something, so when in doubt terminate the TLS since we don't really have
|
||||||
// 1. SNI-based Proxy / Tunnel (we don't even need to put it through the tlsSocket)
|
// any good place to default to when proxying.
|
||||||
// 2. Admin Interface (skips the proxying)
|
if (!handled) {
|
||||||
// 3. Terminated (goes on to a particular module or route)
|
tlsRouter.terminate(socket);
|
||||||
//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();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
, get: function getTcpRouter(address, port) {
|
|
||||||
address = address || '0.0.0.0';
|
|
||||||
|
|
||||||
var id = address + ':' + port;
|
, processSocket: function (socket, firstChunk, opts) {
|
||||||
if (!tlsRouter._map[id]) {
|
if (opts.hyperPeek) {
|
||||||
tlsRouter._map[id] = tlsRouter._create(address, port);
|
// 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]*/) {
|
if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) {
|
||||||
console.log('tryTls');
|
console.log('tryTls');
|
||||||
opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid';
|
opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid';
|
||||||
tlsRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts);
|
tlsRouter.processSocket(conn, firstChunk, opts);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue