-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: proxy broker according to https_proxy and no_proxy environment …
…variables
- Loading branch information
1 parent
b6b0554
commit e5f54ca
Showing
4 changed files
with
148 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); |