Skip to content
This repository has been archived by the owner on Apr 21, 2020. It is now read-only.

Commit

Permalink
Add JWT validator support.
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanbreen committed Sep 27, 2015
1 parent 9d135b4 commit e151b4a
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 12 deletions.
8 changes: 8 additions & 0 deletions lib/proxy/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ function ProxyConfig(config) {
}
Object.defineProperty(this_obj, 'mode', { 'value': mode, writable: false, enumerable: true });

// oauth_[reverse_|]proxy supports multiple types of authentication. The default is oauth1a, but JWT is also
// supported.
var type = 'oauth1a';
if (config.type === 'jwt') {
type = config.type;
}
Object.defineProperty(this_obj, 'type', { 'value': type, writable: false, enumerable: true });

// By default, assume we are reverse proxying for a service running on localhost.
var target_host = 'localhost';
if (config.target_host) {
Expand Down
60 changes: 48 additions & 12 deletions lib/proxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,129 +29,165 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

/**
* Ordered list of mutators, validators, parsers, and other middleware for a proxy or reverse proxy.
* @param {Object} this_obj – A `this` Object to bind to.
* @param {Boolean} is_reverse_proxy - A falsey value (default) results in only forward proxy middleware being
* returned. A truthy value results in only reverse proxy middleware being returned.
* @param {Object} this_obj – The proxy object.
* @returns {Object[]} middleware_list - Array of anonymous objects with `Object.value` containing an object evaluated
* using the passed in parameters. `Object.(forward|reverse)_proxy === true` iff the middleware should be used for a
* forward or reverse proxy, respectively. Order matters!
*/
function getMiddlewareList(this_obj, is_reverse_proxy) {
// is_reverse_proxy = (is_reverse_proxy === true) || false;
function getMiddlewareList(this_obj) {
var middleware_list = [
{
name: 'request_sanity_validator',
desc: 'Test for minimum viable sanity for an inbound request. Pass in the proxy object so that the URI and Host header can be matched against the expected values, if provided.',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./validators/request_sanity_validator.js')(this_obj)
}, {
name: 'url_length_validator',
desc: 'Reject request with URLs longer than 16kb.',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./validators/url_length_validator.js')(this_obj)
}, {
name: 'form_parser',
desc: 'Unpack the body of POSTs so we can use them in signatures. Note that this will implicitly limit POST size to 1mb. We may wish to add configuration around this in the future.',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./mutators/urlencoded_bodyparser.js')(this_obj)
}, {
name: 'proxy_request_router',
desc: 'Parse the inbound request to figure out its destination and desired key. Rewrite the request to include that information before signing and remove any control information sent to the proxy.',
forward_proxy: true,
reverse_proxy: false,
oauth1a: true,
jwt: true,
value: require('./mutators/proxy_request_router.js')(this_obj)
}, {
name: 'query_string_parser',
desc: 'Parse query string',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./mutators/query_parser.js')(this_obj)
}, {
name: 'url_parser',
desc: 'Parse url once so that its available in a clean format for the oauth validator.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./mutators/url_parser.js')(this_obj)
}, {
name: 'oauth_param_collector',
desc: 'Gather the oauth params from the request',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: false,
value: require('./mutators/oauth_param_collector.js')(this_obj)
}, {
name: 'forward_header_mutator',
desc: 'Parse url once so that its available in a clean format for the oauth param generator. \
Modify the request headers to add x-forwarded-*',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./mutators/forward_header_mutator.js')(this_obj)
}, {
name: 'whitelist_validator',
desc: 'Check the request against our path/verb whitelist.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./validators/whitelist_validator.js')(this_obj)
}, {
name: 'oauth_param_sanity_validator',
desc: 'Validate that the oauth params pass a set of viability checks (existence, version, etc).',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: false,
value: require('./validators/oauth_param_sanity_validator.js')(this_obj)
}, {
name: 'quota_validator',
desc: 'Validate that the request is within quota.',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./validators/quota_validator.js')(this_obj)
}, {
name: 'oauth_timestamp_validator',
desc: 'Validate that the timestamp of the request is legal.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: false,
value: require('./validators/oauth_timestamp_validator.js')(this_obj)
}, {
name: 'oauth_signature_validator',
desc: 'Perform the oauth signature validation.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: false,
value: require('./validators/oauth_signature_validator.js')(this_obj)
}, {
name: 'jwt_validator',
desc: 'Perform the JWT validation.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: false,
jwt: true,
value: require('./validators/json_web_token_validator.js')(this_obj)
}, {
name: 'oauth_param_generator',
desc: 'Validate that the timestamp of the request is legal',
forward_proxy: true,
reverse_proxy: false,
oauth1a: true,
jwt: false,
value: require('./mutators/oauth_param_generator.js')(this_obj)
}, {
name: 'host_header_mutator',
desc: 'Update the host header.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./mutators/host_header_mutator.js')(this_obj)
}, {
name: 'auth_header_remover',
desc: 'We have no need for the auth header and dont want to proxy it on to the target host, so strip it.',
forward_proxy: false,
reverse_proxy: true,
oauth1a: true,
jwt: false,
value: require('./mutators/auth_header_remover.js')(this_obj)
}, {
name: 'restreamer',
desc: 'Since connect messes with the input parameters and we want to pass them through unadulterated to the target, we need to add restreamer to the chain.',
forward_proxy: true,
reverse_proxy: true,
oauth1a: true,
jwt: true,
value: require('./mutators/restreamer.js')({stringify: querystring.stringify})
},


];

var is_reverse_proxy = this_obj.config.isReverseProxy();
return _.filter(middleware_list, function(m) {
if (is_reverse_proxy === true)
return (m.reverse_proxy === true);
else
return (m.forward_proxy === true);
return m[this_obj.config.type] &&
((is_reverse_proxy && m.reverse_proxy) || (!is_reverse_proxy && m.forward_proxy));
});
}

Expand Down Expand Up @@ -206,7 +242,7 @@ Proxy.prototype.getConnectApp = function() {
// Apply the appropriate middleware to `connect()`.
var app = connect();
var is_reverse_proxy = this.config.isReverseProxy();
var middleware_list = getMiddlewareList(this, is_reverse_proxy);
var middleware_list = getMiddlewareList(this);
_.each(middleware_list, function(middleware) {
app.use(middleware.value);
});
Expand Down
36 changes: 36 additions & 0 deletions lib/proxy/validators/json_web_token_validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
var jwt = require('express-jwt');

var unauthorized = require('../messages/unauthorized.js');

var module_tag = {
module: require('../../logger.js').getModulePath(__filename)
};

/**
* Create a JWT validator using the proxy's keystore to lookup secrets based
* on issued.
*/
module.exports = function(proxy) {
var keys = proxy.keystore.keys;

return function(req, res, next) {
var jwt_validator = jwt({
secret: function(req, payload, done) {
// TODO: We need to validate that this was set already
var issuer = payload.iss;
if (issuer === undefined) return unauthorized(proxy.logger, req, res, "No issuer specified");
if (keys[issuer] === undefined) return unauthorized(proxy.logger, req, res, "Invalid issuer specified");
done(null, keys[issuer]);
}
});

jwt_validator(req, res, function(err) {
if (err) return unauthorized(proxy.logger, req, res, "JSON web token validation error " + err);

// TODO: Grab req.user.admin and create a header out of it?

// We passed validation, so move to the next step in the pipeline.
next();
});
};
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"body-parser": "^1.14.0",
"bunyan": "^1.5.1",
"connect": "3.4.0",
"express-jwt": "^3.1.0",
"http-proxy": "^1.11.2",
"mkdirp": "0.5.0",
"node-uuid": "1.4.3",
Expand Down
11 changes: 11 additions & 0 deletions test/config.d/jwt_jobs_service.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"service_name": "jwt_jobsservice",
"type": "jwt",
"from_port": 7070,
"to_port": 8080,
"oauth_secret_dir": "./test/keys/8008/8080/",
"required_uris": [
"/getProducts","/uploads","/multipart","/chunked","/compressed","/job","/live","/health","/transactions","/%7bwonky%20path%7d/"
],
"required_hosts": [ "localhost", "::1" ]
}

0 comments on commit e151b4a

Please sign in to comment.