Skip to content

Commit

Permalink
Merge pull request hapijs#891 from wpreul/master
Browse files Browse the repository at this point in the history
Exposing rejectUnauthorized property on proxy
  • Loading branch information
Eran Hammer committed May 29, 2013
2 parents 8c864fd + e99b651 commit 7c6e722
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 38 deletions.
3 changes: 3 additions & 0 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ The following options are available when adding a route:
- `'http'`
- `'https'`
- `passThrough` - if `true`, forwards the headers sent from the client to the upstream service being proxied to. Defaults to `false`.
- `rejectUnauthorized` - sets the _'rejectUnauthorized'_ property on the https [agent](http://nodejs.org/api/https.html#https_https_request_options_callback) making the request.
This value is only used when the proxied server uses TLS/SSL. When set it will override the node.js _'rejectUnauthorized'_ property. If _'false'_ then ssl errors will be ignored.
When _'true'_ the server certificate is verified and an 500 response will be sent when verification fails. Defaults to the https agent default value of _'true'_.
- `xforward` - if `true`, sets the 'X-Forwarded-For', 'X-Forwarded-Port', 'X-Forwarded-Proto' headers when making a request to the
proxied upstream endpoint. Defaults to `false`.
- `redirects` - the maximum number of HTTP redirections allowed, to be followed automatically by the handler. Set to `false` or `0` to
Expand Down
4 changes: 4 additions & 0 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ exports.request = function (method, url, options, callback, _trace) {
uri.method = method.toUpperCase();
uri.headers = options.headers;

if (options.rejectUnauthorized !== undefined && uri.protocol === 'https:') {
uri.rejectUnauthorized = options.rejectUnauthorized;
}

var redirects = (options.hasOwnProperty('redirects') ? options.redirects : false); // Needed to allow 0 as valid value when passed recursively

_trace = (_trace || []);
Expand Down
7 changes: 6 additions & 1 deletion lib/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ exports = module.exports = internals.Proxy = function (options, route) {
this.settings.isCustomPostResponse = !!options.postResponse;
this.settings.postResponse = options.postResponse || internals.postResponse; // function (request, settings, response, payload)
this.settings.redirects = options.redirects || false;

if (options.rejectUnauthorized !== undefined) {
this.settings.rejectUnauthorized = options.rejectUnauthorized;
}
};


Expand All @@ -51,7 +55,8 @@ internals.Proxy.prototype.handler = function () {
headers: {},
payload: null,
redirects: self.settings.redirects,
timeout: self.settings.timeout
timeout: self.settings.timeout,
rejectUnauthorized: self.settings.rejectUnauthorized
};

if (self.settings.passThrough) { // Never set with cache
Expand Down
129 changes: 94 additions & 35 deletions test/integration/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ var it = Lab.test;
describe('Proxy', function () {

var server = null;
var sslServer = null;

before(function (done) {

var tlsOptions = {
key: '-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANysie374iGH54SVcmM4vb+CjN4nVVCmL6af9XOUxTqq/50CBn+Z\nZol0XDG+OK55HTOht4CsQrAXey69ZTxgUMcCAwEAAQJAX5t5XtxkiraA/hZpqsdo\nnlKHibBs7DY0KvLeuybXlKS3ar/0Uz0OSJ1oLx3d0KDSmcdAIrfnyFuBNuBzb3/J\nEQIhAPX/dh9azhztRppR+9j8CxDg4ixJ4iZbHdK0pfnY9oIFAiEA5aV8edK31dkF\nfBXoqlOvIeuNc6WBZrYjUNspH8M+BVsCIQDZF3U6/nve81bXYXqMZwGtB4kR5LH7\nf3W2OU4wS9RfsQIhAJkNB76xX3AYqX0fpOcPyuLSeH2gynNH5JWY2vmeSBGNAiAm\nLon4E3M/IrVVvpxGRFOazKlgIsQFGAaoylDrRFYgBA==\n-----END RSA PRIVATE KEY-----\n',
cert: '-----BEGIN CERTIFICATE-----\nMIIB0TCCAXugAwIBAgIJANGtTMK5HBUIMA0GCSqGSIb3DQEBBQUAMEQxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UECgwJaGFwaSB0ZXN0MRQwEgYDVQQD\nDAtleGFtcGxlLmNvbTAeFw0xMzA0MDQxNDQ4MDJaFw0yMzA0MDIxNDQ4MDJaMEQx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UECgwJaGFwaSB0ZXN0MRQw\nEgYDVQQDDAtleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDcrInt\n++Ihh+eElXJjOL2/gozeJ1VQpi+mn/VzlMU6qv+dAgZ/mWaJdFwxvjiueR0zobeA\nrEKwF3suvWU8YFDHAgMBAAGjUDBOMB0GA1UdDgQWBBQBOiF6iL2PI4E6PBj071Dh\nAiQOGjAfBgNVHSMEGDAWgBQBOiF6iL2PI4E6PBj071DhAiQOGjAMBgNVHRMEBTAD\nAQH/MA0GCSqGSIb3DQEBBQUAA0EAw8Y2rpM8SUQXjgaJJmFXrfEvnl/he7q83K9W\n9Sr/QLHpCFxunWVd8c0wz+b8P/F9uW2V4wUf5NWj1UdHMCd6wQ==\n-----END CERTIFICATE-----\n'
};

var mapUriWithError = function (request, callback) {

return callback(new Error('myerror'));
Expand Down Expand Up @@ -128,6 +134,11 @@ describe('Proxy', function () {
}, 10);
};

var sslHandler = function (req) {

req.reply('Ok');
};

var upstream = new Hapi.Server(0);
upstream.route([
{ method: 'GET', path: '/profile', handler: profile },
Expand All @@ -150,48 +161,68 @@ describe('Proxy', function () {
{ method: 'GET', path: '/timeout2', handler: timeoutHandler }
]);

var upstreamSsl = new Hapi.Server(0, { tls: tlsOptions });
upstreamSsl.route([
{ method: 'GET', path: '/', handler: sslHandler }
]);

var mapUri = function (request, callback) {

return callback(null, upstream.info.uri + request.path + (request.url.search || ''), { 'x-super-special': '@' });
};

upstream.start(function () {
var mapSslUri = function (request, callback) {

var backendPort = upstream.info.port;
var routeCache = { expiresIn: 500, mode: 'server+client' };

server = new Hapi.Server(0, { cors: true, maxSockets: 10 });
server.route([
{ method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
{ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } } },
{ method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: backendPort } } },
{ method: 'GET', path: '/proxyerror', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'GET', path: '/postResponseError', handler: { proxy: { host: 'localhost', port: backendPort, postResponse: postResponseWithError } }, config: { cache: routeCache } },
{ method: 'GET', path: '/errorResponse', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'POST', path: '/echo', handler: { proxy: { mapUri: mapUri } } },
{ method: 'POST', path: '/file', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { payload: 'stream' } },
{ method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } },
{ method: 'GET', path: '/forward', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
{ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
{ method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: backendPort } } },
{ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
{ method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
{ method: 'GET', path: '/google', handler: { proxy: { mapUri: function (request, callback) { callback(null, 'http://www.google.com'); } } } },
{ method: 'GET', path: '/googler', handler: { proxy: { mapUri: function (request, callback) { callback(null, 'http://google.com'); }, redirects: 1 } } },
{ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true, redirects: 2 } } },
{ method: 'POST', path: '/post1', handler: { proxy: { host: 'localhost', port: backendPort, redirects: 3 } }, config: { payload: 'stream' } },
{ method: 'GET', path: '/nowhere', handler: { proxy: { host: 'no.such.domain.x8' } } },
{ method: 'GET', path: '/cached', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: backendPort, timeout: 5 } } },
{ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: backendPort } } }
]);

server.state('auto', { autoValue: 'xyz' });
server.start(function () {
return callback(null, 'https://127.0.0.1:' + upstreamSsl.info.port);
};

done();
upstream.start(function () {

upstreamSsl.start(function () {

var backendPort = upstream.info.port;
var routeCache = { expiresIn: 500, mode: 'server+client' };

server = new Hapi.Server(0, { cors: true, maxSockets: 10 });
server.route([
{ method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
{ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } } },
{ method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: backendPort } } },
{ method: 'GET', path: '/proxyerror', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'GET', path: '/postResponseError', handler: { proxy: { host: 'localhost', port: backendPort, postResponse: postResponseWithError } }, config: { cache: routeCache } },
{ method: 'GET', path: '/errorResponse', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'POST', path: '/echo', handler: { proxy: { mapUri: mapUri } } },
{ method: 'POST', path: '/file', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { payload: 'stream' } },
{ method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } },
{ method: 'GET', path: '/forward', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
{ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
{ method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: backendPort } } },
{ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
{ method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
{ method: 'GET', path: '/google', handler: { proxy: { mapUri: function (request, callback) { callback(null, 'http://www.google.com'); } } } },
{ method: 'GET', path: '/googler', handler: { proxy: { mapUri: function (request, callback) { callback(null, 'http://google.com'); }, redirects: 1 } } },
{ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true, redirects: 2 } } },
{ method: 'POST', path: '/post1', handler: { proxy: { host: 'localhost', port: backendPort, redirects: 3 } }, config: { payload: 'stream' } },
{ method: 'GET', path: '/nowhere', handler: { proxy: { host: 'no.such.domain.x8' } } },
{ method: 'GET', path: '/cached', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
{ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: backendPort, timeout: 5 } } },
{ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: backendPort } } }
]);

sslServer = new Hapi.Server(0);
sslServer.route([
{ method: 'GET', path: '/allow', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: false } } },
{ method: 'GET', path: '/reject', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: true } } },
{ method: 'GET', path: '/sslDefault', handler: { proxy: { mapUri: mapSslUri } } }
]);

server.state('auto', { autoValue: 'xyz' });
server.start(function () {

sslServer.start(done);
});
});
});
});
Expand Down Expand Up @@ -486,5 +517,33 @@ describe('Proxy', function () {
done();
});
});

it('uses rejectUnauthorized to allow proxy to self signed ssl server', function (done) {

sslServer.inject('/allow', function (res) {

expect(res.statusCode).to.equal(200);
expect(res.payload).to.equal('Ok');
done();
});
});

it('uses rejectUnauthorized to not allow proxy to self signed ssl server', function (done) {

sslServer.inject('/reject', function (res) {

expect(res.statusCode).to.equal(500);
done();
});
});

it('the default rejectUnauthorized should not allow proxied server cert to be self signed', function (done) {

sslServer.inject('/sslDefault', function (res) {

expect(res.statusCode).to.equal(500);
done();
});
});
});

3 changes: 1 addition & 2 deletions test/unit/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('Client', function () {

server.once('listening', function () {

Client.request('get', 'http://127.0.0.1:' + server.address().port, { payload: 'asdadqweqweqweqewqweqweqeqweqewqwe' }, function (err) {
Client.request('get', 'http://127.0.0.1:' + server.address().port, { payload: '' }, function (err) {

expect(err.data.err.code).to.equal('ECONNRESET');
done();
Expand Down Expand Up @@ -120,7 +120,6 @@ describe('Client', function () {

it('handles errors when remote server is unavailable', function (done) {


Client.request('get', 'http://127.0.0.1:0', { payload: '' }, function (err) {

expect(err).to.exist;
Expand Down

0 comments on commit 7c6e722

Please sign in to comment.