diff --git a/lib/commands/deploy_endpoint.js b/lib/commands/deploy_endpoint.js index c267f3b7e..5a9ba374b 100644 --- a/lib/commands/deploy_endpoint.js +++ b/lib/commands/deploy_endpoint.js @@ -463,7 +463,7 @@ ApiDeployer.prototype._buildEndpoints = Promise.method(function() { .bind(_this) .then(_this._createEndpointMethod) .then(_this._createEndpointIntegration) - //.then(_this._updateLambdaPermission) + .then(_this._manageLambdaAccessPolicy) .then(_this._createEndpointMethodResponses) .then(_this._createEndpointMethodIntegResponses) .then(function() { @@ -590,12 +590,6 @@ ApiDeployer.prototype._createEndpointMethod = Promise.method(function(endpoint) } } - //console.log( - // 'Creating method with parent ID: ' - // + endpoint.apiGateway.cloudFormation.Method - // + ' ' - // + endpoint.apiGateway.apig.resource.id); - return _this.ApiClient.showMethod( _this._restApiId, endpoint.apiGateway.apig.resource.id, @@ -683,7 +677,7 @@ ApiDeployer.prototype._createEndpointIntegration = Promise.method(function(endpo // Until API Gateway is fixed, we need to make a seperate call to Lambda to add credentials to API Gateway // Once API Gateway is fixed, we can use this in credentials: // _this._regionJson.iamRoleArnApiGateway - credentials: _this._regionJson.iamRoleArnApiGateway, + credentials: null, requestParameters: endpoint.apiGateway.cloudFormation.RequestParameters || {}, requestTemplates: endpoint.apiGateway.cloudFormation.RequestTemplates || {}, cacheNamespace: endpoint.apiGateway.cloudFormation.CacheNamespace || null, @@ -832,8 +826,21 @@ ApiDeployer.prototype._createEndpointMethodIntegResponses = Promise.method(funct }); }); +ApiDeployer.prototype._manageLambdaAccessPolicy = Promise.method(function(endpoint) { + + var _this = this; + + // If method integration is not for a lambda, skip + if (!endpoint.apiGateway.apig.lambda) return endpoint; + + return _this._getLambdaAccessPolicy(endpoint) + .bind(_this) + .then(_this._removeLambdaAccessPolicy) + .then(_this._updateLambdaAccessPolicy); +}); + /** - * API Deployer: Update Lambda Permission + * API Deployer: Get Lambda Access Policy * - Since specifying credentials when creating the Method Integration results in ~500ms * - of extra latency, this function updates the lambda's access policy instead * - to grant API Gateway permission. This is how the API Gateway console does it. @@ -841,40 +848,117 @@ ApiDeployer.prototype._createEndpointMethodIntegResponses = Promise.method(funct * - is currently impossible to implement. */ -ApiDeployer.prototype._updateLambdaPermission = Promise.method(function(endpoint) { +ApiDeployer.prototype._getLambdaAccessPolicy = Promise.method(function(endpoint) { var _this = this; - var lambdas; - console.log(endpoint.apiGateway.apig.lambda.PhysicalResourceId); - // TODO: Finish when AWS "getPolicy" bug is fixed - // Get policy for lambda - // Check to see if it already has a JAWS policy - // Replace the JAWS policy - // All done! - - return AWSUtils.lambdaListFunctions( + return AWSUtils.lambdaGetPolicy( _this._JAWS._meta.profile, - _this._regionJson.region) + _this._regionJson.region, + endpoint.apiGateway.apig.lambda.PhysicalResourceId) .then(function(data) { - lambdas = data.Functions; - console.log(data.Functions); + endpoint.apiGateway.apig.lambda.Policy = JSON.parse(data.Policy); + return endpoint; }) - .then(function() { + .catch(function(error) { + return endpoint; + }); +}); - console.log(endpoint.apiGateway.apig.lambda.PhysicalResourceId); - console.log(endpoint.apiGateway.apig.lambda); - return AWSUtils.lambdaGetPolicy( - _this._JAWS._meta.profile, - _this._regionJson.region, - endpoint.apiGateway.apig.lambda.PhysicalResourceId) - .then(function(data) { +/** + * Remove Lambda Access Policy + */ - var policy = JSON.parse(data.Policy); +ApiDeployer.prototype._removeLambdaAccessPolicy = Promise.method(function(endpoint) { - console.log('lambda policy: ', policy.Statement[0]); - return endpoint; - }); + var _this = this; + var statement; + + if (endpoint.apiGateway.apig.lambda.Policy) { + + var policy = endpoint.apiGateway.apig.lambda.Policy; + + for (var i = 0; i < policy.Statement.length; i++) { + statement = policy.Statement[i]; + if (statement.Sid && statement.Sid === 'jaws-apigateway-access') continue; + } + } + + if (!statement) return endpoint; + + return AWSUtils.lambdaRemovePermission( + _this._JAWS._meta.profile, + _this._regionJson.region, + endpoint.apiGateway.apig.lambda.PhysicalResourceId, + 'jaws-apigateway-access') + .then(function(data) { + + JawsCli.log( + 'Endpoint Deployer: "' + + _this._stage + + ' - ' + + _this._regionJson.region + + ' - ' + + endpoint.apiGateway.cloudFormation.Path + + '": removed existing lambda access policy statement'); + + return endpoint; + }) + .catch(function(error) { + console.log(error); + return endpoint; + }); +}); + +/** + * Update Lambda Access Policy + */ + +ApiDeployer.prototype._updateLambdaAccessPolicy = Promise.method(function(endpoint) { + + var _this = this; + + // Sanitize Path - Remove first and last slashes, if any + endpoint.apiGateway.cloudFormation.Path = endpoint.apiGateway.cloudFormation.Path.split('/'); + endpoint.apiGateway.cloudFormation.Path = endpoint.apiGateway.cloudFormation.Path.join('/'); + + // Create new access policy statement + var statement = {}; + statement.Action = 'lambda:InvokeFunction'; + statement.FunctionName = endpoint.apiGateway.apig.lambda.PhysicalResourceId; + statement.Principal = 'apigateway.amazonaws.com'; + statement.StatementId = 'jaws-apigateway-access'; + statement.SourceArn = 'arn:aws:execute-api:' + + _this._regionJson.region + + ':' + + _this._awsAccountNumber + + ':' + + _this._restApiId + + '/' + + _this._stage + + '/' + + endpoint.apiGateway.cloudFormation.Method + + '/' + + endpoint.apiGateway.cloudFormation.Path; + + return AWSUtils.lambdaAddPermission( + _this._JAWS._meta.profile, + _this._regionJson.region, + statement) + .then(function(data) { + JawsCli.log( + 'Endpoint Deployer: "' + + _this._stage + + ' - ' + + _this._regionJson.region + + ' - ' + + endpoint.apiGateway.cloudFormation.Path + + '": created new lambda access policy statement'); + return endpoint; + }) + .catch(function(error) { + console.log(error); + return endpoint; }); }); diff --git a/lib/utils/aws.js b/lib/utils/aws.js index 325a14111..416d4a137 100644 --- a/lib/utils/aws.js +++ b/lib/utils/aws.js @@ -807,7 +807,6 @@ exports.lambdaListFunctions = function(awsProfile, awsRegion) { }); }; - /** * Lambda: Get Policy * @param awsProfile @@ -837,3 +836,60 @@ exports.lambdaGetPolicy = function(awsProfile, awsRegion, functionName) { }); }); }; + +/** + * Lambda: Add Permission + * @param awsProfile + * @param awsRegion + * @param bucketName + * @returns {*} + */ + +exports.lambdaAddPermission = function(awsProfile, awsRegion, permissionStatement) { + this.configAWS(awsProfile, awsRegion); + + var lambda = new AWS.Lambda(); + + return new Promise(function(resolve, reject) { + lambda.addPermission(permissionStatement, function(err, data) { + + if (err) { + return reject(err); + } + + return resolve(data); + + }); + }); +}; + +/** + * Lambda: Remove Permission + * @param awsProfile + * @param awsRegion + * @param bucketName + * @returns {*} + */ + +exports.lambdaRemovePermission = function(awsProfile, awsRegion, functionName, statementId) { + this.configAWS(awsProfile, awsRegion); + + var lambda = new AWS.Lambda(); + + var params = { + FunctionName: functionName, /* required */ + StatementId: statementId /* required */ + }; + + return new Promise(function(resolve, reject) { + lambda.removePermission(params, function(err, data) { + + if (err) { + return reject(err); + } + + return resolve(data); + + }); + }); +}; diff --git a/tests/all.js b/tests/all.js index 88851f585..175505d12 100644 --- a/tests/all.js +++ b/tests/all.js @@ -16,7 +16,7 @@ describe('AllTests', function() { //require tests vs inline so we can run sequentially //require('./cli/tag'); - require('./cli/module_install'); + //require('./cli/module_install'); //require('./cli/env'); //require('./cli/module_create'); //require('./cli/run'); @@ -24,7 +24,7 @@ describe('AllTests', function() { /** * Tests below create AWS Resources */ - //require('./cli/dash'); + require('./cli/dash'); //require('./cli/deploy_lambda'); //require('./cli/deploy_endpoint'); //require('./cli/new_stage_region');