improved feedback for bad TLS/TCP gateways
This commit is contained in:
parent
e24f9412dd
commit
5777a885a4
|
@ -81,6 +81,7 @@ module.exports.create = function (deps, config) {
|
|||
|
||||
function createTcpForwarder(mod) {
|
||||
var destination = mod.address.split(':');
|
||||
var connected = false;
|
||||
|
||||
return function (conn) {
|
||||
var newConn = deps.net.createConnection({
|
||||
|
@ -91,22 +92,28 @@ module.exports.create = function (deps, config) {
|
|||
, remoteAddress: conn.remoteAddress
|
||||
, remotePort: conn.remotePort
|
||||
}, function () {
|
||||
connected = true;
|
||||
|
||||
newConn.pipe(conn);
|
||||
conn.pipe(newConn);
|
||||
});
|
||||
|
||||
// Not sure how to effectively report this to the user or client, but we need to listen
|
||||
// for the event to prevent it from crashing us.
|
||||
newConn.on('error', function (err) {
|
||||
console.error('TCP forward connection error', err);
|
||||
if (connected) {
|
||||
console.error('TCP forward remote error', err);
|
||||
conn.end();
|
||||
} else {
|
||||
console.log('TCP forward connection error', err);
|
||||
require('./proxy-err-resp').sendBadGateway(conn, err, config.debug);
|
||||
}
|
||||
});
|
||||
conn.on('error', function (err) {
|
||||
console.error('TCP forward client error', err);
|
||||
newConn.end();
|
||||
});
|
||||
|
||||
newConn.pipe(conn);
|
||||
conn.pipe(newConn);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -69,14 +69,10 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
|
|||
|
||||
proxy.on('error', function (err, req, res) {
|
||||
console.log(err);
|
||||
res.writeHead(502);
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
res.end('The connection was refused. Most likely the service being connected to '
|
||||
+ 'has stopped running or the configuration is wrong.');
|
||||
}
|
||||
else {
|
||||
res.end('Bad Gateway: ' + err.code);
|
||||
}
|
||||
res.statusCode = 502;
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Connection', 'close');
|
||||
res.end(require('../proxy-err-resp').getRespBody(err, conf.debug));
|
||||
});
|
||||
|
||||
return function (req, res, next) {
|
||||
|
|
|
@ -4,6 +4,7 @@ module.exports.create = function (deps, config, netHandler) {
|
|||
var tls = require('tls');
|
||||
var parseSni = require('sni');
|
||||
var greenlock = require('greenlock');
|
||||
var localhostCerts = require('localhost.daplie.me-certificates');
|
||||
var domainMatches = require('../match-domain').match;
|
||||
|
||||
function extractSocketProp(socket, propName) {
|
||||
|
@ -14,6 +15,34 @@ module.exports.create = function (deps, config, netHandler) {
|
|||
;
|
||||
}
|
||||
|
||||
function wrapSocket(socket, opts) {
|
||||
var myDuplex = require('tunnel-packer').Stream.create(socket);
|
||||
myDuplex.remoteFamily = opts.remoteFamily || myDuplex.remoteFamily;
|
||||
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();
|
||||
});
|
||||
|
||||
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(opts.firstChunk);
|
||||
});
|
||||
|
||||
return myDuplex;
|
||||
}
|
||||
|
||||
var le = greenlock.create({
|
||||
// server: 'staging'
|
||||
server: 'https://acme-v01.api.letsencrypt.org/directory'
|
||||
|
@ -105,7 +134,7 @@ module.exports.create = function (deps, config, netHandler) {
|
|||
if (/.*localhost.*\.daplie\.me/.test(sni.toLowerCase())) {
|
||||
// TODO implement
|
||||
if (!secureContexts[sni]) {
|
||||
tlsOptions = require('localhost.daplie.me-certificates').mergeTlsOptions(sni, {});
|
||||
tlsOptions = localhostCerts.mergeTlsOptions(sni, {});
|
||||
}
|
||||
if (tlsOptions) {
|
||||
secureContexts[sni] = tls.createSecureContext(tlsOptions);
|
||||
|
@ -120,7 +149,7 @@ module.exports.create = function (deps, config, netHandler) {
|
|||
le.tlsOptions.SNICallback(sni, cb);
|
||||
};
|
||||
|
||||
var terminator = tls.createServer(terminatorOpts, function (socket) {
|
||||
var terminateServer = tls.createServer(terminatorOpts, function (socket) {
|
||||
console.log('(pre-terminated) tls connection, addr:', socket.remoteAddress);
|
||||
|
||||
netHandler(socket, {
|
||||
|
@ -135,6 +164,7 @@ module.exports.create = function (deps, config, netHandler) {
|
|||
|
||||
function proxy(socket, opts, mod) {
|
||||
var destination = mod.address.split(':');
|
||||
var connected = false;
|
||||
|
||||
var newConn = deps.net.createConnection({
|
||||
port: destination[1]
|
||||
|
@ -145,62 +175,60 @@ module.exports.create = function (deps, config, netHandler) {
|
|||
, remoteFamily: opts.family || extractSocketProp(socket, 'remoteFamily')
|
||||
, remoteAddress: opts.address || extractSocketProp(socket, 'remoteAddress')
|
||||
, remotePort: opts.port || extractSocketProp(socket, 'remotePort')
|
||||
}, function () {
|
||||
connected = true;
|
||||
if (!opts.hyperPeek) {
|
||||
newConn.write(opts.firstChunk);
|
||||
}
|
||||
newConn.pipe(socket);
|
||||
socket.pipe(newConn);
|
||||
});
|
||||
|
||||
// Not sure how to effectively report this to the user or client, but we need to listen
|
||||
// for the event to prevent it from crashing us.
|
||||
newConn.on('error', function (err) {
|
||||
console.error('TLS proxy connection error', err);
|
||||
if (connected) {
|
||||
console.error('TLS proxy remote error', err);
|
||||
socket.end();
|
||||
} else {
|
||||
console.log('TLS proxy connection error', err);
|
||||
var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true});
|
||||
var decrypted;
|
||||
if (opts.hyperPeek) {
|
||||
decrypted = new tls.TLSSocket(socket, tlsOpts);
|
||||
} else {
|
||||
decrypted = new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts);
|
||||
}
|
||||
require('../proxy-err-resp').sendBadGateway(decrypted, err, config.debug);
|
||||
}
|
||||
});
|
||||
socket.on('error', function (err) {
|
||||
console.error('TLS proxy client error', err);
|
||||
newConn.end();
|
||||
});
|
||||
|
||||
newConn.write(opts.firstChunk);
|
||||
newConn.pipe(socket);
|
||||
socket.pipe(newConn);
|
||||
}
|
||||
|
||||
function terminate(socket, opts) {
|
||||
console.log('[tls-terminate] ' + opts.localAddress || socket.localAddress + ':' + opts.localPort || socket.localPort + ' servername', opts.servername, socket.remoteAddress);
|
||||
console.log(
|
||||
'[tls-terminate]'
|
||||
, opts.localAddress || socket.localAddress +':'+ opts.localPort || socket.localPort
|
||||
, 'servername=' + opts.servername
|
||||
, opts.remoteAddress || socket.remoteAddress
|
||||
);
|
||||
|
||||
if (opts.hyperPeek) {
|
||||
// This connection was peeked at using a method that doesn't interferre with the TLS
|
||||
// server's ability to handle it properly. Currently the only way this happens is
|
||||
// with tunnel connections where we have the first chunk of data before creating the
|
||||
// new connection (thus removing need to get data off the new connection).
|
||||
terminator.emit('connection', socket);
|
||||
return;
|
||||
terminateServer.emit('connection', socket);
|
||||
}
|
||||
|
||||
else {
|
||||
// The hyperPeek flag wasn't set, so we had to read data off of this connection, which
|
||||
// means we can no longer use it directly in the TLS server.
|
||||
// See https://github.com/nodejs/node/issues/8752 (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();
|
||||
});
|
||||
|
||||
terminator.emit('connection', myDuplex);
|
||||
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(opts.firstChunk);
|
||||
});
|
||||
terminateServer.emit('connection', wrapSocket(socket, opts));
|
||||
}
|
||||
}
|
||||
|
||||
function handleConn(socket, opts) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
function getRespBody(err, debug) {
|
||||
if (debug) {
|
||||
return err.toString();
|
||||
}
|
||||
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
return 'The connection was refused. Most likely the service being connected to '
|
||||
+ 'has stopped running or the configuration is wrong.';
|
||||
}
|
||||
|
||||
return 'Bad Gateway: ' + err.code;
|
||||
}
|
||||
|
||||
function sendBadGateway(conn, err, debug) {
|
||||
var body = getRespBody(err, debug);
|
||||
|
||||
conn.write([
|
||||
'HTTP/1.1 502 Bad Gateway'
|
||||
, 'Date: ' + (new Date()).toUTCString()
|
||||
, 'Connection: close'
|
||||
, 'Content-Type: text/html'
|
||||
, 'Content-Length: ' + body.length
|
||||
, ''
|
||||
, body
|
||||
].join('\r\n'));
|
||||
conn.end();
|
||||
}
|
||||
|
||||
module.exports.getRespBody = getRespBody;
|
||||
module.exports.sendBadGateway = sendBadGateway;
|
Loading…
Reference in New Issue