forked from coolaj86/goldilocks.js
		
	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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user