mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
Merge pull request #2732 from serverless/limit-permissions
Limited Lambda Permission for Events
This commit is contained in:
commit
2cb4dc4dc5
@ -70,7 +70,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This
|
||||
|IAM::Policy | IamPolicyLambdaExecution | IamPolicyLambdaExecution |
|
||||
|Lambda::Function | {normalizedFunctionName}LambdaFunction | HelloLambdaFunction |
|
||||
|Logs::LogGroup | {normalizedFunctionName}LogGroup | HelloLogGroup |
|
||||
|Lambda::Permission | <ul><li>**Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index} </li><li>**S3**: {normalizedFunctionName}LambdaPermissionS3</li><li>**APIG**: {normalizedFunctionName}LambdaPermissionApiGateway</li><li>**SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}</li> | <ul><li>**Schedule**: HelloLambdaPermissionEventsRuleSchedule1 </li><li>**S3**: HelloLambdaPermissionS3</li><li>**APIG**: HelloLambdaPermissionApiGateway</li><li>**SNS**: HelloLambdaPermissionSometopic</li> |
|
||||
|Lambda::Permission | <ul><li>**Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index} </li><li>**S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3</li><li>**APIG**: {normalizedFunctionName}LambdaPermissionApiGateway</li><li>**SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS</li> | <ul><li>**Schedule**: HelloLambdaPermissionEventsRuleSchedule1 </li><li>**S3**: HelloLambdaPermissionBucketS3</li><li>**APIG**: HelloLambdaPermissionApiGateway</li><li>**SNS**: HelloLambdaPermissionTopicSNS</li> |
|
||||
|Events::Rule | {normalizedFuntionName}EventsRuleSchedule{SequentialID} | HelloEventsRuleSchedule1 |
|
||||
|ApiGateway::RestApi | ApiGatewayRestApi | ApiGatewayRestApi |
|
||||
|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers |
|
||||
|
||||
@ -7,6 +7,7 @@ module.exports = {
|
||||
|
||||
compileMethods() {
|
||||
this.apiGatewayMethodLogicalIds = [];
|
||||
this.permissionMapping = [];
|
||||
|
||||
this.validated.events.forEach((event) => {
|
||||
const resourceId = this.getResourceId(event.http.path);
|
||||
@ -27,14 +28,20 @@ module.exports = {
|
||||
template.Properties.ApiKeyRequired = true;
|
||||
}
|
||||
|
||||
_.merge(template,
|
||||
this.getMethodAuthorization(event.http),
|
||||
this.getMethodIntegration(event.http, event.functionName),
|
||||
this.getMethodResponses(event.http)
|
||||
);
|
||||
|
||||
const methodLogicalId = this.provider.naming
|
||||
.getMethodLogicalId(resourceName, event.http.method);
|
||||
const lambdaLogicalId = this.provider.naming
|
||||
.getLambdaLogicalId(event.functionName);
|
||||
|
||||
const singlePermissionMapping = { resourceName, lambdaLogicalId, event };
|
||||
this.permissionMapping.push(singlePermissionMapping);
|
||||
|
||||
_.merge(template,
|
||||
this.getMethodAuthorization(event.http),
|
||||
this.getMethodIntegration(event.http, lambdaLogicalId, methodLogicalId),
|
||||
this.getMethodResponses(event.http)
|
||||
);
|
||||
|
||||
this.apiGatewayMethodLogicalIds.push(methodLogicalId);
|
||||
|
||||
|
||||
@ -3,9 +3,7 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
getMethodIntegration(http, functionName) {
|
||||
const lambdaLogicalId = this.provider.naming
|
||||
.getLambdaLogicalId(functionName);
|
||||
getMethodIntegration(http, lambdaLogicalId) {
|
||||
const integration = {
|
||||
IntegrationHttpMethod: 'POST',
|
||||
Type: http.integration,
|
||||
|
||||
@ -6,27 +6,36 @@ const BbPromise = require('bluebird');
|
||||
module.exports = {
|
||||
|
||||
compilePermissions() {
|
||||
this.validated.events.forEach((event) => {
|
||||
this.permissionMapping.forEach((singlePermissionMapping) => {
|
||||
const lambdaPermissionLogicalId = this.provider.naming
|
||||
.getLambdaApiGatewayPermissionLogicalId(event.functionName);
|
||||
const lambdaLogicalId = this.provider.naming
|
||||
.getLambdaLogicalId(event.functionName);
|
||||
.getLambdaApiGatewayPermissionLogicalId(singlePermissionMapping.event.functionName);
|
||||
|
||||
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
|
||||
[lambdaPermissionLogicalId]: {
|
||||
Type: 'AWS::Lambda::Permission',
|
||||
Properties: {
|
||||
FunctionName: {
|
||||
'Fn::GetAtt': [lambdaLogicalId, 'Arn'],
|
||||
'Fn::GetAtt': [singlePermissionMapping.lambdaLogicalId, 'Arn'],
|
||||
},
|
||||
Action: 'lambda:InvokeFunction',
|
||||
Principal: 'apigateway.amazonaws.com',
|
||||
SourceArn: { 'Fn::Join': ['',
|
||||
[
|
||||
'arn:aws:execute-api:',
|
||||
{ Ref: 'AWS::Region' },
|
||||
':',
|
||||
{ Ref: 'AWS::AccountId' },
|
||||
':',
|
||||
{ Ref: this.apiGatewayRestApiLogicalId },
|
||||
'/*/*',
|
||||
],
|
||||
] },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (event.http.authorizer) {
|
||||
const authorizer = event.http.authorizer;
|
||||
if (singlePermissionMapping.event.http.authorizer) {
|
||||
const authorizer = singlePermissionMapping.event.http.authorizer;
|
||||
const authorizerPermissionLogicalId = this.provider.naming
|
||||
.getLambdaApiGatewayPermissionLogicalId(authorizer.name);
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ describe('#awsCompilePermissions()', () => {
|
||||
awsCompileApigEvents.validated = {};
|
||||
});
|
||||
|
||||
it('should create permission resource when http events are given', () => {
|
||||
it('should create limited permission resource scope to REST API', () => {
|
||||
awsCompileApigEvents.validated.events = [
|
||||
{
|
||||
functionName: 'First',
|
||||
@ -26,17 +26,19 @@ describe('#awsCompilePermissions()', () => {
|
||||
path: 'foo/bar',
|
||||
method: 'post',
|
||||
},
|
||||
}, {
|
||||
functionName: 'First',
|
||||
http: {
|
||||
path: 'foo/bar',
|
||||
method: 'get',
|
||||
},
|
||||
}, {
|
||||
functionName: 'Second',
|
||||
http: {
|
||||
path: 'bar/foo',
|
||||
method: 'get',
|
||||
},
|
||||
];
|
||||
awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
|
||||
awsCompileApigEvents.permissionMapping = [
|
||||
{
|
||||
lambdaLogicalId: 'FirstLambdaFunction',
|
||||
resourceName: 'FooBar',
|
||||
event: {
|
||||
http: {
|
||||
path: 'foo/bar',
|
||||
method: 'post',
|
||||
},
|
||||
functionName: 'First',
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -45,9 +47,22 @@ describe('#awsCompilePermissions()', () => {
|
||||
expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.FirstLambdaPermissionApiGateway
|
||||
.Properties.FunctionName['Fn::GetAtt'][0]).to.equal('FirstLambdaFunction');
|
||||
|
||||
const deepObj = { 'Fn::Join': ['',
|
||||
[
|
||||
'arn:aws:execute-api:',
|
||||
{ Ref: 'AWS::Region' },
|
||||
':',
|
||||
{ Ref: 'AWS::AccountId' },
|
||||
':',
|
||||
{ Ref: 'ApiGatewayRestApi' },
|
||||
'/*/*',
|
||||
],
|
||||
] };
|
||||
|
||||
expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.SecondLambdaPermissionApiGateway
|
||||
.Properties.FunctionName['Fn::GetAtt'][0]).to.equal('SecondLambdaFunction');
|
||||
.Resources.FirstLambdaPermissionApiGateway
|
||||
.Properties.SourceArn).to.deep.equal(deepObj);
|
||||
});
|
||||
});
|
||||
|
||||
@ -65,6 +80,24 @@ describe('#awsCompilePermissions()', () => {
|
||||
},
|
||||
},
|
||||
];
|
||||
awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
|
||||
awsCompileApigEvents.permissionMapping = [
|
||||
{
|
||||
lambdaLogicalId: 'FirstLambdaFunction',
|
||||
resourceName: 'FooBar',
|
||||
event: {
|
||||
http: {
|
||||
authorizer: {
|
||||
name: 'authorizer',
|
||||
arn: { 'Fn::GetAtt': ['AuthorizerLambdaFunction', 'Arn'] },
|
||||
},
|
||||
path: 'foo/bar',
|
||||
method: 'post',
|
||||
},
|
||||
functionName: 'First',
|
||||
},
|
||||
},
|
||||
];
|
||||
return awsCompileApigEvents.compilePermissions().then(() => {
|
||||
expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.AuthorizerLambdaPermissionApiGateway
|
||||
@ -74,6 +107,7 @@ describe('#awsCompilePermissions()', () => {
|
||||
|
||||
it('should not create permission resources when http events are not given', () => {
|
||||
awsCompileApigEvents.validated.events = [];
|
||||
awsCompileApigEvents.permissionMapping = [];
|
||||
return awsCompileApigEvents.compilePermissions().then(() => {
|
||||
expect(
|
||||
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
|
||||
|
||||
@ -119,7 +119,8 @@ class AwsCompileS3Events {
|
||||
filter
|
||||
);
|
||||
}
|
||||
s3EnabledFunctions.push(functionName);
|
||||
const s3EnabledFunction = { functionName, bucketName };
|
||||
s3EnabledFunctions.push(s3EnabledFunction);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -150,9 +151,9 @@ class AwsCompileS3Events {
|
||||
// iterate over all functions with S3 events
|
||||
// and give S3 permission to invoke them all
|
||||
// by adding Lambda::Permission resource for each
|
||||
s3EnabledFunctions.forEach(functionName => {
|
||||
s3EnabledFunctions.forEach(s3EnabledFunction => {
|
||||
const lambdaLogicalId = this.provider.naming
|
||||
.getLambdaLogicalId(functionName);
|
||||
.getLambdaLogicalId(s3EnabledFunction.functionName);
|
||||
const permissionTemplate = {
|
||||
Type: 'AWS::Lambda::Permission',
|
||||
Properties: {
|
||||
@ -164,10 +165,16 @@ class AwsCompileS3Events {
|
||||
},
|
||||
Action: 'lambda:InvokeFunction',
|
||||
Principal: 's3.amazonaws.com',
|
||||
SourceArn: { 'Fn::Join': ['',
|
||||
[
|
||||
`arn:aws:s3:::${s3EnabledFunction.bucketName}`,
|
||||
],
|
||||
] },
|
||||
},
|
||||
};
|
||||
const lambdaPermissionLogicalId = this.provider.naming
|
||||
.getLambdaS3PermissionLogicalId(functionName);
|
||||
.getLambdaS3PermissionLogicalId(s3EnabledFunction.functionName,
|
||||
s3EnabledFunction.bucketName);
|
||||
const permissionCFResource = {
|
||||
[lambdaPermissionLogicalId]: permissionTemplate,
|
||||
};
|
||||
|
||||
@ -118,7 +118,10 @@ describe('AwsCompileS3Events', () => {
|
||||
.Resources.S3BucketFirstfunctionbuckettwo.Type
|
||||
).to.equal('AWS::S3::Bucket');
|
||||
expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.FirstLambdaPermissionS3.Type
|
||||
.Resources.FirstLambdaPermissionFirstfunctionbucketoneS3.Type
|
||||
).to.equal('AWS::Lambda::Permission');
|
||||
expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.FirstLambdaPermissionFirstfunctionbuckettwoS3.Type
|
||||
).to.equal('AWS::Lambda::Permission');
|
||||
expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.S3BucketFirstfunctionbuckettwo.Properties.NotificationConfiguration
|
||||
@ -156,7 +159,7 @@ describe('AwsCompileS3Events', () => {
|
||||
.Resources.S3BucketFirstfunctionbucketone.Properties.NotificationConfiguration
|
||||
.LambdaConfigurations.length).to.equal(2);
|
||||
expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.FirstLambdaPermissionS3.Type
|
||||
.Resources.FirstLambdaPermissionFirstfunctionbucketoneS3.Type
|
||||
).to.equal('AWS::Lambda::Permission');
|
||||
});
|
||||
|
||||
|
||||
@ -79,7 +79,17 @@ class AwsCompileSNSEvents {
|
||||
"Properties": {
|
||||
"FunctionName": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] },
|
||||
"Action": "lambda:InvokeFunction",
|
||||
"Principal": "sns.amazonaws.com"
|
||||
"Principal": "sns.amazonaws.com",
|
||||
"SourceArn": {
|
||||
"Fn::Join": ["",
|
||||
["arn:aws:sns:",
|
||||
{ "Ref": "AWS::Region"},
|
||||
":",
|
||||
{ "Ref": "AWS::AccountId"},
|
||||
":",
|
||||
"${topicName}"]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -62,10 +62,10 @@ describe('AwsCompileSNSEvents', () => {
|
||||
.provider.compiledCloudFormationTemplate.Resources.SNSTopicTopic2.Type
|
||||
).to.equal('AWS::SNS::Topic');
|
||||
expect(awsCompileSNSEvents.serverless.service
|
||||
.provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1.Type
|
||||
.provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1SNS.Type
|
||||
).to.equal('AWS::Lambda::Permission');
|
||||
expect(awsCompileSNSEvents.serverless.service
|
||||
.provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic2.Type
|
||||
.provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic2SNS.Type
|
||||
).to.equal('AWS::Lambda::Permission');
|
||||
});
|
||||
|
||||
@ -96,7 +96,7 @@ describe('AwsCompileSNSEvents', () => {
|
||||
.Properties.Subscription.length
|
||||
).to.equal(2);
|
||||
expect(awsCompileSNSEvents.serverless.service
|
||||
.provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1.Type
|
||||
.provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1SNS.Type
|
||||
).to.equal('AWS::Lambda::Permission');
|
||||
});
|
||||
|
||||
|
||||
@ -136,12 +136,13 @@ module.exports = {
|
||||
},
|
||||
|
||||
// Permissions
|
||||
getLambdaS3PermissionLogicalId(functionName) {
|
||||
return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionS3`;
|
||||
getLambdaS3PermissionLogicalId(functionName, bucketName) {
|
||||
return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${this
|
||||
.normalizeBucketName(bucketName)}S3`;
|
||||
},
|
||||
getLambdaSnsPermissionLogicalId(functionName, topicName) {
|
||||
return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${
|
||||
this.normalizeTopicName(topicName)}`;
|
||||
this.normalizeTopicName(topicName)}SNS`;
|
||||
},
|
||||
getLambdaSchedulePermissionLogicalId(functionName, scheduleIndex) {
|
||||
return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionEventsRuleSchedule${
|
||||
|
||||
@ -325,16 +325,16 @@ describe('#naming()', () => {
|
||||
|
||||
describe('#getLambdaS3PermissionLogicalId()', () => {
|
||||
it('should normalize the function name and add the standard suffix', () => {
|
||||
expect(sdk.naming.getLambdaS3PermissionLogicalId('functionName'))
|
||||
.to.equal('FunctionNameLambdaPermissionS3');
|
||||
expect(sdk.naming.getLambdaS3PermissionLogicalId('functionName', 'bucket'))
|
||||
.to.equal('FunctionNameLambdaPermissionBucketS3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getLambdaSnsPermissionLogicalId()', () => {
|
||||
it('should normalize the function and topic names and add them as prefix and suffix to the ' +
|
||||
'standard permission center', () => {
|
||||
expect(sdk.naming.getLambdaSnsPermissionLogicalId('functionName', 'topicName'))
|
||||
.to.equal('FunctionNameLambdaPermissionTopicName');
|
||||
expect(sdk.naming.getLambdaSnsPermissionLogicalId('functionName', 'topic'))
|
||||
.to.equal('FunctionNameLambdaPermissionTopicSNS');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user