diff --git a/README.md b/README.md index c2643d2..e94dc64 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,27 @@ The rules object contains a set of key-value pairs mapping a regex-supported url ## Other Notes * `(?:\\W|$)` is appended to the end of the regex-supported url path, so that if there is a key like `.*/test` in the rules, the module matches paths `/test`, `/test/`, `/test?` but not `/testing`. * As long as object keys continued to be ordered in V8, if there are multiple rules that match against a given url path, the module will pick the matching rule listed first for the translation. + +## IP Whitelisting +At times there is a need to block proxy requests from ip addresses you have not already whitelisted. To support this use case you may now provide an object to apply additional 'rules' for proxying. + + +```js + // Set up proxy rules instance + var proxyRules = new HttpProxyRules({ + rules: { + '.*/ipTest1/': { + 'whitelist': [ // only allow whitelist + "127.0.0.1" // Explicit IP Address Whitelisted + ], + 'target': 'http://localhost:8080/cool3/' + }, + '.*/ipTest2/': { + 'whitelist': [ // only allow whitelist + "8.8.8.0/24" //CIDR IP Range notation + ], + 'target': 'http://localhost:8080/cool4/' + }, + +``` +Note the two options available. You may provide an IP address to allow or you may also provide Classless Inter-Doman Routing notation. For more information on CIDR notation see the wikipedia article [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation] diff --git a/example/simple.js b/example/simple.js index 7bbf296..0de2797 100644 --- a/example/simple.js +++ b/example/simple.js @@ -9,7 +9,25 @@ module.exports = function spawnReverseProxy(cb) { var proxyRules = new HttpProxyRules({ rules: { '.*/test': 'http://localhost:8080/cool', // Rule (1) - '.*/test2/': 'http://localhost:8080/cool2/' // Rule (2) + '.*/test2/': 'http://localhost:8080/cool2/', // Rule (2) + '.*/ipTest1/': { // Rule (3) + 'whitelist': [ + "127.0.0.1/24" // only allow whitelist + ], + 'target': 'http://localhost:8080/cool3/' + }, + '.*/ipTest2/': { // Rule (4) + 'whitelist': [ + "8.8.8.0/24" // only allow whitelist + ], + 'target': 'http://localhost:8080/cool4/' + }, + '.*/ipTest3/': { // Rule (5) + 'whitelist': [ + "127.0.0.1" // only allow whitelist + ], + 'target': 'http://localhost:8080/cool5/' + } }, default: 'http://localhost:8080' // default target }); @@ -32,6 +50,6 @@ module.exports = function spawnReverseProxy(cb) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('The request url and path did not match any of the listed rules!'); - }).listen(6010, cb); + }).listen(6010, '0.0.0.0', cb); }; diff --git a/index.js b/index.js index 24e82bf..796612f 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ - +var rangeCheck = require('range_check'); +var requestIp = require('request-ip'); /** * This is a constructor for a HttpProxyRules instance. * @param {Object} options Takes in a `rules` obj, (optional) `default` target @@ -6,7 +7,7 @@ function HttpProxyRules(options) { this.rules = options.rules; this.default = options.default || null; - + return this; }; @@ -41,7 +42,26 @@ HttpProxyRules.prototype.match = function match(req) { if (testPrefixMatch && testPrefixMatch.index === 0) { urlPrefix = pathEndsWithSlash ? testPrefixMatch[0] : testPrefixMatch[1]; req.url = path.replace(urlPrefix, ''); - target = rules[pathPrefix]; + // Check to see if value of key value pair is an object + if (typeof rules[pathPrefix] === 'object') { + // Check to see if there is ip matching logic in place + var ip = requestIp.getClientIp(req); + if (rules[pathPrefix].whitelist) { + for (var range in rules[pathPrefix].whitelist) { + if (rules[pathPrefix].whitelist.hasOwnProperty(range)) { + if ((rules[pathPrefix].whitelist[range].indexOf('/') > -1 && rangeCheck.inRange(ip, rules[pathPrefix].whitelist[range])) + || (rules[pathPrefix].whitelist[range] === ip)) { + target = rules[pathPrefix].target; + } + } + } + } else { + target = rules[pathPrefix].target; + } + } else { + target = rules[pathPrefix]; + } + break; } } diff --git a/package.json b/package.json index e82ac22..4b0c4e5 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,16 @@ "mocha": "^2.3.4", "request": "^2.67.0" }, - "keywords": ["node-http-proxy", "http-proxy", "http-proxy-table", "proxy-table", "proxy-rules", "proxy"] + "keywords": [ + "node-http-proxy", + "http-proxy", + "http-proxy-table", + "proxy-table", + "proxy-rules", + "proxy" + ], + "dependencies": { + "range_check": "^1.2.0", + "request-ip": "^1.1.4" + } } diff --git a/test/index.tests.js b/test/index.tests.js index d31fda4..a1940ae 100644 --- a/test/index.tests.js +++ b/test/index.tests.js @@ -21,7 +21,7 @@ describe('Proxy Routes', function () { // response includes the url that you tried to access translatedPath: req.url })); - }).listen(targetPort, function mockServerReady() { + }).listen(targetPort, '0.0.0.0', function mockServerReady() { spawnReverseProxy(function proxyServerReady() { done(); // call done to start running test suite }); @@ -32,6 +32,40 @@ describe('Proxy Routes', function () { // runs after all tests in this block done(); }); + + it('should allow whitelisted ipv4 and deny non-whitelisted ipv4 addresses', function(done) { + async.parallel([ + function(cb) { + request({ + url: 'http://127.0.0.1:' + proxyServerPort + "/ipTest1/", + json: true + }, function processResp(err, res, body) { + expect(res.statusCode).to.equal(200); + cb(); + }); + }, + function(cb) { + request({ + url: 'http://127.0.0.1:' + proxyServerPort + "/ipTest2/", + json: true + }, function processResp(err, res, body) { + expect(body.translatedPath).to.not.equal(''); + cb(); + }); + }, + function(cb) { + request({ + url: 'http://127.0.0.1:' + proxyServerPort + "/ipTest3/", + json: true + }, function processResp(err, res, body) { + expect(body.translatedPath).to.not.equal(''); + cb(); + }); + } + ], function(){ + done(); + }); + }); it('should translate the url correctly', function (done) {