Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for securing webtask execution #197

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions bin/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ module.exports = Cli.createCommand('create', {
description: 'Allow the webtask to be called using a custom domain name. Using this option requires proof of domain ownership. This can be done by adding a TXT record type to the DNS of the chosen domain. The value of the record must be `webtask:container:{container}`, where {container} is the webtask container name to be associated with the custom domain. Many such TXT records can be created as needed.',
type: 'string'
},
'auth': {
description: `Enable securing of the execution of a webtask using the specified methodology for example '--auth jwt'.\n\nAdditional metadata can be specified when using this flag:\n- Set the 'wt-execution-scope' metadata property to the name of a custom scope that can be used for authorization of webtask execution.\n- Set the 'wt-execution-aud' metadata property which identifies the recipients that the JWT is intended for (requires 'wt-execution-iss' to be specified as well).\n- Set the 'wt-execution-iss' metadata property which identifies the principal that issued the JWT (requires 'wt-execution-aud' to be specified as well). \n\n’wt-execution-aud' and ‘wt-execution-iss’ can be omitted if using Extend API V2.`,
type: 'string'
}
},
},
params: {
Expand Down
2 changes: 1 addition & 1 deletion bin/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ module.exports = Cli.createCommand('update', {
'capture': {
description: 'Download and use the current code indicated by `url`. When you are developing a webtask whose code is remotely hosted, this option will automatically download the remote code before creating the webtask. This means that the webtask will continue to run even if the remote url becomes unavailable.',
type: 'boolean',
},
}
},
},
params: {
Expand Down
11 changes: 11 additions & 0 deletions lib/constans.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const MIDDLWARE_COMPILER = '@webtask/middleware-compiler';
const MIDDLWARE_COMPILER_VERSION = '^1.5.0';
const JWT_MIDDLEWARE = '@webtask/jwt-middleware';
const JWT_MIDDLEWARE_VERSION = '^1.5.0';

module.exports = {
MIDDLWARE_COMPILER,
MIDDLWARE_COMPILER_VERSION,
JWT_MIDDLEWARE,
JWT_MIDDLEWARE_VERSION
}
64 changes: 64 additions & 0 deletions lib/secureWebtask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const _ = require('lodash');
const Promise = require('bluebird');
const request = require('superagent');
const Cli = require('structured-cli');
const constants = require('./constans');

module.exports = (args, options) => {

if (args.meta['wt-execution-iss'] && args.meta['wt-execution-aud']) {

addMeta(args, options);

return Promise.resolve();

} else {

return request.get(args.profile.url + '/api/description')
.then(res => {
extractBody(args, res, options);
})
.catch(err => {
throw new Cli.error.serverError(`Unable to secure webtask: ${err.message}`);
});
}
}

function extractBody(args, res, options) {

if (!res.body) {
throw new Cli.error.serverError(`Unable to read response from the discovery endpoint.`);
}

if (!res.body.authorization_server || !res.body.audience) {
throw new Cli.error.serverError(`Unable to read response from the discovery endpoint.`);
}

args.meta['wt-execution-iss'] = res.body.authorization_server;
args.meta['wt-execution-aud'] = res.body.audience;

addMeta(args, options);
}

function addMeta(args, options) {

args.meta['wt-authorize-execution'] = "1";

if (!args.meta['wt-compiler']) {
args.meta['wt-compiler'] = constants.MIDDLWARE_COMPILER
}

let wt_middleware = [];
if (args.meta['wt-middleware']) {
wt_middleware = args.meta['wt-middleware'].split(",");
args.meta['wt-middleware'] = _.union(wt_middleware, [constants.JWT_MIDDLEWARE]).join(',');
} else {
args.meta['wt-middleware'] = constants.JWT_MIDDLEWARE;
}

if (!args.syntheticDependencies[constants.MIDDLWARE_COMPILER]) {
args.syntheticDependencies[constants.MIDDLWARE_COMPILER] = constants.MIDDLWARE_COMPILER_VERSION;
}

args.syntheticDependencies[constants.JWT_MIDDLEWARE] = constants.JWT_MIDDLEWARE_VERSION;
}
15 changes: 10 additions & 5 deletions lib/validateCreateArgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ const Fs = require('fs');
const Path = require('path');
const Util = require('./util');
const keyValList2Object = require('./keyValList2Object');
const constants = require('./constans');

const MIDDLWARE_COMPILER = '@webtask/middleware-compiler';
const MIDDLWARE_COMPILER_VERSION = '^1.2.1';
const VALID_DNS_REGEX = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
const VALID_IP_REGEX = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
const VALID_JTN_REGEX = /^[-a-z0-9._~!$+=:@]+$/i;
Expand Down Expand Up @@ -174,16 +173,22 @@ function validateCreateArgs(args) {
}
}


// this check needs to happen after metaFile is parsed
if (args.auth == 'jwt' && args.profile.securityVersion === "v1" && (!args.meta['wt-execution-aud'] || !args.meta['wt-execution-iss'])) {
throw Cli.error.invalid(`Both 'wt-execution-aud' and 'wt-execution-iss' metadata properties are required for deployments with V1 security enabled.`);
}

if (args.middleware.length) {
if (args.meta && args.meta['wt-compiler'] && args.meta['wt-compiler'] !== MIDDLWARE_COMPILER) {
if (args.meta && args.meta['wt-compiler'] && args.meta['wt-compiler'] !== constants.MIDDLWARE_COMPILER) {
throw Cli.error.invalid('Use of middleware is incompatible with a custom webtask compiler.');
}
const filter = mw => _.startsWith(mw, 'http') ? 'urls' : 'modules';
const groups = _.groupBy(args.middleware, filter);
const middleware = [];

args.meta['wt-compiler'] = MIDDLWARE_COMPILER;
args.syntheticDependencies[MIDDLWARE_COMPILER] = MIDDLWARE_COMPILER_VERSION;
args.meta['wt-compiler'] = constants.MIDDLWARE_COMPILER;
args.syntheticDependencies[constants.MIDDLWARE_COMPILER] = constants.MIDDLWARE_COMPILER_VERSION;

(groups.modules||[])
.map(Util.parseMiddleware)
Expand Down
16 changes: 12 additions & 4 deletions lib/webtaskCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Sandbox = require('sandboxjs');
const Superagent = require('superagent');
const Watcher = require('filewatcher');
const _ = require('lodash');
const SecureWebtask = require('../lib/secureWebtask');

module.exports = createWebtaskCreator;

Expand Down Expand Up @@ -100,12 +101,19 @@ function createWebtaskCreator(args, options) {
return Promise.resolve([]);
}



function createWebtask(profile) {
return args.source === 'url'
? createSimpleWebtask(profile)
: createLocalWebtask(profile);

return args['auth'] === 'jwt'
? SecureWebtask(args, {}).then(() => {
return args.source === 'url'
? createSimpleWebtask(profile)
: createLocalWebtask(profile);
})
: args.source === 'url'
? createSimpleWebtask(profile)
: createLocalWebtask(profile);
}

function createSimpleWebtask(profile, generation) {
Expand Down