Merge branch 'master' into master
diff --git a/accesscontrol/index.js b/accesscontrol/index.js
new file mode 100644
index 0000000..b9e0cfb
--- /dev/null
+++ b/accesscontrol/index.js
@@ -0,0 +1,135 @@
+'use strict';
+/**
+ * This plugin whitelists or blacklists source ip addresses
+ */
+
+var debug = require('debug')('plugin:accesscontrol');
+var util = require("util");
+const dns = require('dns');
+
+module.exports.init = function (config, logger, stats) {
+
+ var allow;
+ var deny;
+
+ /**
+ * This method reads allow and/or deby lists from the config.yaml
+ * applies the appropriate rule on the incoming message
+ */
+ function checkAccessControlInfo(sourceIP) {
+ if (config === null) debug('WARNING: insufficient information to run accesscontrol');
+ else if (config.allow === null && config.deny === null) debug('WARNING: insufficient information to run accesscontrol');
+ else if (config.allow != null) {
+ debug ('allow list: ' + util.inspect(config.allow, 2, true));
+ if (scanIP(config.allow, sourceIP)) {
+ allow = true;
+ }
+ }
+ else if (config.deny != null) {
+ debug ('deny list: ' + util.inspect(config.deny, 2, true));
+ if (scanIP(config.deny, sourceIP)) {
+ debug ('deny incoming message');
+ deny = true;
+ }
+ }
+ }
+
+ /**
+ * check if the parameter is valid IPv4 address
+ */
+ function checkIsIPV4(entry) {
+ var blocks = entry.split(".");
+ if(blocks.length === 4) {
+ return blocks.every(function(block) {
+ return (block === '*' || (parseInt(block,10) >=0 && parseInt(block,10) <= 255));
+ });
+ }
+ return false;
+ }
+
+ /**
+ * for each list in the allow and deny, make sure they are proper
+ * IPv4 addresses
+ */
+ function validateIPList(list) {
+ list.forEach(function(entry){
+ if (!checkIsIPV4(entry)) return false;
+ });
+ return true;
+ }
+
+ function scanIP(list, sourceIP) {
+
+ var sourceOctets = sourceIP.split('.');
+ //no wildcard
+ for (var i=0; i < list.length; i++) {
+ //no wildcard
+ if (list[i].indexOf('*') == -1 && list[i] == sourceIP) {
+ return true;
+ } else if (list[i].indexOf('*') != -1) { //contains wildcard
+ var listOctets = list[i].split('.');
+ if (octetCompare(listOctets, sourceOctets)) return true;
+ }
+ }
+ //did not match any in the list
+ return false;
+ }
+
+ /**
+ * the allow or deny list contains a wildcard. perform octet level
+ * comparision
+ */
+ function octetCompare (listOctets, sourceOctets) {
+ var compare = false;
+ for (var i=0; i < listOctets.length; i++) {
+ //debug('list ' + listOctets[i] + ' sourceOctets ' + sourceOctets[i]);
+ if (listOctets[i] != '*' && parseInt(listOctets[i]) == parseInt(sourceOctets[i])) {
+ compare = true;
+ } else if (listOctets[i] != '*' && parseInt(listOctets[i]) != parseInt(sourceOctets[i])) {
+ return false;
+ }
+ }
+ return compare;
+ }
+
+ /**
+ * send error message to the user
+ */
+ function sendError(res) {
+ var errorInfo = {
+ "code": "403",
+ "message": "Forbidden"
+ };
+ res.writeHead(403, { 'Content-Type': 'application/json' });
+ res.write(JSON.stringify(errorInfo));
+ res.end();
+ }
+
+ return {
+
+ onrequest: function(req, res, next) {
+ debug('plugin onrequest');
+ var host = req.headers.host;
+ debug ("source ip " + host);
+ var sourceIP = host.split(":");
+
+ if (checkIsIPV4(sourceIP[0])) {
+ checkAccessControlInfo(sourceIP[0]);
+ if (allow === false || deny === true)
+ sendError(res);
+ } else {
+ dns.lookup(sourceIP[0], (err, address, family) => {
+ debug('address: %j family: IPv%s', address, family);
+ if (err) {
+ debug(err);
+ sendError(res);
+ }
+ checkAccessControlInfo(address);
+ if (allow === false || deny === true)
+ sendError(res);
+ });
+ }
+ next();
+ }
+ };
+}
\ No newline at end of file
diff --git a/accesscontrol/package.json b/accesscontrol/package.json
new file mode 100644
index 0000000..696da2b
--- /dev/null
+++ b/accesscontrol/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "edgemicro-access-control",
+ "version": "1.0.0",
+ "description": "This provides ip based whitelisting or blacklisting",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "srinandan",
+ "license": "Apache 2.0",
+ "dependencies": {
+ "debug": "^2.2.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/apigee/microgateway-plugins.git"
+ },
+ "bugs": {
+ "url": "https://github.com/apigee/microgateway-plugins/issues"
+ },
+ "homepage": "https://github.com/apigee/microgateway-plugins#readme"
+}
diff --git a/accesscontrol/test/accesscontrol-test.js b/accesscontrol/test/accesscontrol-test.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/accesscontrol/test/accesscontrol-test.js
diff --git a/eurekaclient/eureka-client.json b/eurekaclient/eureka-client.json
new file mode 100644
index 0000000..9f18ea1
--- /dev/null
+++ b/eurekaclient/eureka-client.json
@@ -0,0 +1,15 @@
+{
+ "instance":{
+ "app": "microgateway",
+ "vipAddress": "microgateway",
+ "dataCenterInfo":{
+ "name": "MyOwn"
+ },
+ "port": {}
+ },
+ "eureka":{
+ "host": "localhost",
+ "port": 8761,
+ "servicePath": "/eureka/apps/"
+ }
+}
\ No newline at end of file
diff --git a/eurekaclient/index.js b/eurekaclient/index.js
new file mode 100644
index 0000000..b582390
--- /dev/null
+++ b/eurekaclient/index.js
@@ -0,0 +1,95 @@
+'use strict';
+/**
+ * This plugin integrate with Eureka and gets the target
+ * endpoint dynamically.
+ */
+
+var debug = require('debug')('plugin:eurekeclient');
+var util = require('util');
+var os = require('os');
+
+const port = 8000;
+const Eureka = require('eureka-js-client').Eureka;
+
+module.exports.init = function (config, logger, stats) {
+
+ const lookup = config.servicemap;
+
+ config.instance.hostName = os.hostname();
+ debug('local hostName: ' + config.instance.hostName);
+ config.instance.ipAddr = getIPAddr();
+ config.instance.port = {};
+ config.instance.port["$"] = port;
+ config.instance.port["@enabled"] = true;
+ config.instance.dataCenterInfo["@class"] = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo";
+
+ const client = new Eureka(config);
+
+ try {
+ client.start();
+ } catch (err) {
+ console.error(err);
+ client.stop();
+ }
+
+ function getIPAddr() {
+ var interfaces = os.networkInterfaces();
+ var addresses = [];
+ for (var k in interfaces) {
+ for (var k2 in interfaces[k]) {
+ var address = interfaces[k][k2];
+ if (address.family === 'IPv4' && !address.internal) {
+ addresses.push(address.address);
+ }
+ }
+ }
+ debug ('localhost ip: ' + addresses[0]);
+ return addresses[0];
+ }
+
+ function getAppName(url) {
+ for (var index in lookup) {
+ if (url.includes(lookup[index].uri) || url == lookup[index].uri) {
+ return {
+ app: lookup[index].app,
+ secure: lookup[index].secure
+ };
+ }
+ }
+ return "";
+ }
+
+ function getTarget(app, secure) {
+ var instances = client.getInstancesByAppId(app);
+
+ for (var index in instances) {
+ if (instances[index].status == "UP") {
+ return (secure == true) ? {"hostName": instances[index].hostName, "port": instances[index].securePort["$"]} : {"hostName": instances[index].hostName, "port":instances[index].port["$"]};
+ }
+ }
+ return "";
+ }
+
+ return {
+ onrequest: function(req, res, next) {
+ var appInfo = getAppName(req.url);
+ var endpoint = getTarget(appInfo.app, appInfo.secure);
+
+ if (endpoint.hostName) {
+ debug("target hostname: " + endpoint.hostName);
+ req.targetHostname = endpoint.hostName;
+ debug("target port: " + endpoint.port);
+ req.targetPort = endpoint.port;
+ req.targetPath = req.url;
+ if (appInfo.secure) {
+ req.targetSecure = true;
+ } else {
+ req.targetSecure = false;
+ }
+ } else {
+ console.warn("Target enpoint from Eureka not found");
+ }
+ next();
+ }
+ };
+}
diff --git a/eurekaclient/package.json b/eurekaclient/package.json
new file mode 100644
index 0000000..c3443bd
--- /dev/null
+++ b/eurekaclient/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "eurekaclient",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "eureka-js-client": "^4.3.0"
+ }
+}
diff --git a/eurekaclient/servicelookup.json b/eurekaclient/servicelookup.json
new file mode 100644
index 0000000..958dfe4
--- /dev/null
+++ b/eurekaclient/servicelookup.json
@@ -0,0 +1 @@
+[{"uri": "/book", "app": "BOOK", "secure": false},{"uri":"/httpbin", "app": "BOOK", "secure": false}]
\ No newline at end of file
diff --git a/index.js b/index.js
index 9fc1317..fd4f20a 100644
--- a/index.js
+++ b/index.js
@@ -10,6 +10,8 @@
'quota-memory':require('./quota-memory'),
spikearrest:require('./spikearrest'),
'transform-uppercase':require('./transform-uppercase'),
+ accesscontrol:require('./accesscontrol'),
+ eurekaclient:require('./eurekaclient')
json2xml: require('./json2xml'),
healthcheck: require('./healthcheck')
}
diff --git a/oauth/index.js b/oauth/index.js
index 75d4ed0..9521cee 100644
--- a/oauth/index.js
+++ b/oauth/index.js
@@ -22,8 +22,32 @@
var authHeaderName = config['authorization-header'] ? config['authorization-header'] : 'authorization';
var apiKeyHeaderName = config['api-key-header'] ? config['api-key-header'] : 'x-api-key';
var keepAuthHeader = config['keep-authorization-header'] || false;
+ //support for enabling oauth or api key only
+ var oauth_only = config['allowOAuthOnly'] || false;
+ var apikey_only = config['allowAPIKeyOnly'] || false;
+ //
var apiKey;
+ //support for enabling oauth or api key only
+ if (oauth_only) {
+ if (!req.headers['authorization']) {
+ debug('missing_authorization');
+ return sendError(req, res, next, logger, stats, 'missing_authorization', 'Missing Authorization header');
+ } else {
+ var header = authHeaderRegex.exec(req.headers['authorization']);
+ if (!header || header.length < 2) {
+ debug('Invalid Authorization Header');
+ return sendError(req, res, next, logger, stats, 'invalid_request', 'Invalid Authorization header');
+ }
+ }
+ }
+ else if (apikey_only) {
+ if (req.headers[authHeaderName]) {
+ debug('Invalid Authorization Type');
+ return sendError(req, res, next, logger, stats, 'invalid_auth', 'Invalid Authorization type');
+ }
+ }
+ //leaving rest of the code same to ensure backward compatibility
if (!req.headers[authHeaderName]) {
if (apiKey = req.headers[apiKeyHeaderName]) {
exchangeApiKeyForToken(req, res, next, config, logger, stats, middleware, apiKey);