diff --git a/index.js b/index.js index 406e1c1..c34d3c9 100644 --- a/index.js +++ b/index.js @@ -95,39 +95,53 @@ class CustomRoles { return this.getPolicyFromStatements('logging', statements); } - getStreamsPolicy(functionName, functionObj) { + getEventPolicyName(eventType, functionName) { + if (eventType === 'stream') { + return 'streams'; + } + if (eventType === 'sqs') { + return 'queues'; + } + this.log(`WARNING: event type for function '${functionName}' is not configured properly. IAM permissions will not be set properly.`); + return undefined; + } + + getEventPolicy(functionName, functionObj, eventType) { if (!functionObj.events) { return null; } const resources = functionObj.events.reduce((acc, event) => { - if (!event.stream) { + if (!event[eventType]) { return acc; } + const e = event[eventType]; let eventSourceArn; - if (typeof event.stream === 'string') { - eventSourceArn = event.stream; - } else if (typeof event.stream === 'object' && event.stream.arn) { - eventSourceArn = event.stream.arn; + if (typeof e === 'string') { + eventSourceArn = e; + } else if (typeof e === 'object' && e.arn) { + eventSourceArn = e.arn; } if (!eventSourceArn) { - this.log(`WARNING: Stream event source for function '${functionName}' is not configured properly. IAM permissions will not be set properly.`); + this.log(`WARNING: event source for function '${functionName}' is not configured properly. IAM permissions will not be set properly.`); return acc; } - const streamType = event.stream.type || eventSourceArn.split(':')[2]; - if (streamType === 'dynamodb') { + const eventSubType = eventType === 'stream' ? e.type || eventSourceArn.split(':')[2] : undefined; + if (eventType === 'stream' && eventSubType === 'dynamodb') { acc.dynamodb.push(eventSourceArn); - } else if (streamType === 'kinesis') { + } else if (eventType === 'stream' && eventSubType === 'kinesis') { acc.kinesis.push(eventSourceArn); + } else if (eventType === 'sqs') { + acc.sqs.push(eventSourceArn); } else { - this.log(`WARNING: Stream event type for function '${functionName}' is not configured properly. IAM permissions will not be set properly.`); + this.log(`WARNING: event type for function '${functionName}' is not configured properly. IAM permissions will not be set properly.`); } return acc; - }, { dynamodb: [], kinesis: [] }); + }, { dynamodb: [], kinesis: [], sqs: [] }); const statements = []; if (resources.dynamodb.length) { @@ -137,7 +151,7 @@ class CustomRoles { 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', 'dynamodb:DescribeStream', - 'dynamodb:ListStreams' + 'dynamodb:ListStreams', ], Resource: resources.dynamodb }); @@ -149,13 +163,25 @@ class CustomRoles { 'kinesis:GetRecords', 'kinesis:GetShardIterator', 'kinesis:DescribeStream', - 'kinesis:ListStreams' + 'kinesis:ListStreams', ], Resource: resources.kinesis }); } + if (resources.sqs.length) { + statements.push({ + Effect: 'Allow', + Action: [ + 'sqs:ReceiveMessage', + 'sqs:DeleteMessage', + 'sqs:GetQueueAttributes' + ], + Resource: resources.sqs + }); + } - return this.getPolicyFromStatements('streams', statements); + return this.getPolicyFromStatements(this + .getEventPolicyName(eventType, functionName), statements); } getRole(stackName, functionName, policies, managedPolicies, permissionsBoundary) { @@ -239,11 +265,16 @@ class CustomRoles { policies.push(customPolicy); } - const streamsPolicy = this.getStreamsPolicy(functionName, functionObj); + const streamsPolicy = this.getEventPolicy(functionName, functionObj, 'stream'); if (streamsPolicy) { policies.push(streamsPolicy); } + const sqsPolicy = this.getEventPolicy(functionName, functionObj, 'sqs'); + if (sqsPolicy) { + policies.push(sqsPolicy); + } + if (service.provider.vpc || functionObj.vpc) { managedPolicies.push(VPC_POLICY); } diff --git a/test/index-tests.js b/test/index-tests.js index f50119b..0fed666 100644 --- a/test/index-tests.js +++ b/test/index-tests.js @@ -98,7 +98,7 @@ describe('serverless-plugin-custom-roles', function() { }); }); - describe('#getStreamsPolicy', function() { + describe('#getEventPolicy for stream', function() { beforeEach(function() { this.functionName = 'testFunction'; this.instance = createTestInstance(); @@ -107,7 +107,7 @@ describe('serverless-plugin-custom-roles', function() { it('should do nothing if function does not have events defined', function() { const functionObj = {}; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result).to.be.null; sinon.assert.notCalled(this.instance.serverless.cli.log); @@ -120,7 +120,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result).to.be.null; sinon.assert.notCalled(this.instance.serverless.cli.log); @@ -134,7 +134,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result) .to.containSubset({ @@ -167,7 +167,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result) .to.containSubset({ @@ -196,12 +196,12 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result).to.be.null; sinon.assert.calledWithExactly( this.instance.serverless.cli.log, - `[serverless-plugin-custom-roles]: WARNING: Stream event source for function '${this.functionName}' is not configured properly. IAM permissions will not be set properly.` + `[serverless-plugin-custom-roles]: WARNING: event source for function '${this.functionName}' is not configured properly. IAM permissions will not be set properly.` ); }); @@ -216,7 +216,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result) .to.containSubset({ @@ -248,7 +248,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result) .to.containSubset({ @@ -281,7 +281,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result) .to.containSubset({ @@ -314,7 +314,7 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result) .to.containSubset({ @@ -347,12 +347,12 @@ describe('serverless-plugin-custom-roles', function() { }] }; - const result = this.instance.getStreamsPolicy(this.functionName, functionObj); + const result = this.instance.getEventPolicy(this.functionName, functionObj, 'stream'); expect(result).to.be.null; sinon.assert.calledWithExactly( this.instance.serverless.cli.log, - `[serverless-plugin-custom-roles]: WARNING: Stream event type for function '${this.functionName}' is not configured properly. IAM permissions will not be set properly.` + `[serverless-plugin-custom-roles]: WARNING: event type for function '${this.functionName}' is not configured properly. IAM permissions will not be set properly.` ); }); });