Skip to content

Commit

Permalink
Merge pull request #25 from snyk/feat/proxy-support
Browse files Browse the repository at this point in the history
feat: proxy broker according to https_proxy and no_proxy environment …
  • Loading branch information
aviadatsnyk authored Apr 3, 2017
2 parents b6b0554 + e5f54ca commit 24bbd07
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/client/socket.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require('../patch-https-request-for-proxying');

const Primus = require('primus');
const relay = require('../relay');
const httpErrors = require('../http-errors');
Expand Down
102 changes: 102 additions & 0 deletions lib/patch-https-request-for-proxying.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Monkey-patch https.request to proxy request to the broker server
* according the the env variables `https_proxy` and `no_proxy`:
* get the https proxy from the env. proxy requests unless the
* broker server is in the no_proxy list.
* Start from the "Entry point" below.
*/
const url = require('url');
const tunnel = require('tunnel');
const {brokerServerUrl, httpsProxy, noProxy} = require('./config');
const brokerServer = url.parse(brokerServerUrl || '');
brokerServer.port = brokerServer.port ||
brokerServer.protocol === 'https:' ? '443' : '80';

// adapted from https://github.com/request/request/master/lib/getProxyFromURI.js

function formatHostname(hostname) {
// canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
return hostname.replace(/^\.*/, '.').toLowerCase();
}

function parseNoProxyZone(zone) {
zone = zone.trim().toLowerCase();

const zoneParts = zone.split(':', 2);
const zoneHost = formatHostname(zoneParts[0]);
const zonePort = zoneParts[1];
const hasPort = zone.indexOf(':') > -1;

return {hostname: zoneHost, port: zonePort, hasPort: hasPort};
}

function uriInNoProxy(uri, noProxy) {
const port = uri.port || (uri.protocol === 'https:' ? '443' : '80');
const hostname = formatHostname(uri.hostname);
const noProxyList = noProxy.split(',');

// iterate through the noProxyList until it finds a match.
return noProxyList.map(parseNoProxyZone).some(function (noProxyZone) {
const isMatchedAt = hostname.indexOf(noProxyZone.hostname);
const hostnameMatched = (
isMatchedAt > -1 &&
(isMatchedAt === hostname.length - noProxyZone.hostname.length));

if (noProxyZone.hasPort) {
return (port === noProxyZone.port) && hostnameMatched;
}

return hostnameMatched;
});
}

function shouldProxy(uri) {
// Decide the proper request proxy to use based on the request URI object and the
// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
// respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html)

// if no https proxy is defined - don't proxy

if (!httpsProxy) {
return false;
}

// if the noProxy is a wildcard then return null

if (noProxy === '*') {
return false;
}

// if the noProxy is not empty and the uri is found return null

if (noProxy && noProxy !== '' && uriInNoProxy(uri, noProxy)) {
return false;
}

// we should proxy
return true;
}

// Entry point: To patch or not to patch?
if (brokerServer.host && httpsProxy && shouldProxy(brokerServer)) {
const proxy = url.parse(httpsProxy);
const tunnelingAgent = tunnel.httpsOverHttp({
proxy: {
host: proxy.hostname,
port: proxy.port
}
});

// actual monkey patching: BEWARE!
// we're only patching HTTPS requests to the broker server
const https = require('https');
const oldhttpsreq = https.request;
https.request = function (options, callback) {
if (options.host === brokerServer.host) {
options.agent = tunnelingAgent;
}
return oldhttpsreq.call(null, options, callback);
};
}

module.exports = {shouldProxy}; // for testing
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"babel-preset-stage-3": "^6.11.0",
"eslint": "^3.3.1",
"proxyquire": "^1.7.10",
"require-reload": "^0.2.2",
"semantic-release": "^4.3.5",
"sinon": "^1.17.5",
"tap": "^6.3.0",
Expand Down Expand Up @@ -61,6 +62,7 @@
"snyk": "^1.25.2",
"snyk-config": "^1.0.1",
"then-fs": "^2.0.0",
"tunnel": "0.0.4",
"undefsafe": "^1.2.0"
},
"repository": {
Expand Down
42 changes: 42 additions & 0 deletions test/unit/proxying-decision.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const test = require('tap-only');
const u = require('url').parse;
const reload = require('require-reload')(require);

test('shouldProxy: no https proxy', t => {
t.plan(1);
process.env.http_proxy = 'localhost:4444'; // http, not httpS

// loaded now, for config to be reloaded after env vars
const config = reload('../../lib/config');
const {shouldProxy} = reload('../../lib/patch-https-request-for-proxying');
t.false(shouldProxy(u('https://broker.snyk.io')),
'should not proxy when no https proxy is defined');
});

test('shouldProxy: no_proxy', t => {
t.plan(2);
const oldestDomainOnINET = process.env.no_proxy = 'symbolics.com';
process.env.https_proxy = 'https://localhost:8888';

// loaded now, for config to be reloaded after env vars
const config = reload('../../lib/config');
const {shouldProxy} = reload('../../lib/patch-https-request-for-proxying');
const url = u('http://' + oldestDomainOnINET);
t.false(shouldProxy(url),
'should not proxy domains from no_proxy');
t.true(shouldProxy(u('https://shambhala.org/')), 'not in no_proxy');
});

test('shouldProxy: NO_PROXY', t => {
t.plan(2);
const dontProx = 'wiki.c2.com';
process.env.NO_PROXY = 'symbolics.com,' + dontProx;
process.env.HTTPS_PROXY = 'https://localhost:8888';

// loaded now, for config to be reloaded after env vars
const config = reload('../../lib/config');
const {shouldProxy} = reload('../../lib/patch-https-request-for-proxying');
t.false(shouldProxy(u('https://' + dontProx + '/?XeroxParc')),
'should not proxy domains from NO_PROXY');
t.true(shouldProxy(u('http://silibank.net.kp')), 'not in NO_PROXY');
});

0 comments on commit 24bbd07

Please sign in to comment.