deploy endpoints: large change that swaps out how api-gateway gets permission to invoke lambdas.

Instead of specifying an IAM policy when creating the API Gateway method's integration, the individual lambda's access policy is
updated.  The reason for this is that the first method results in an extra ~500ms of latency.  Now, JAWS REST APIs should be much faster.
However, this might result in breaking changes (sorry).
This commit is contained in:
Austen Collins 2015-09-23 17:16:39 -07:00
parent 24a894e217
commit 5b5371b0af
3 changed files with 177 additions and 37 deletions

View File

@ -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;
});
});

View File

@ -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);
});
});
};

View File

@ -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');