diff --git a/README.md b/README.md index d630f4d2..6df8e86c 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ functions: kmsDataKeyReusePeriodSeconds: 600 # optional - AWS default is 300 seconds deadLetterMessageRetentionPeriodSeconds: 1209600 # optional - AWS default is 345600 secs (4 days) deadLetterQueueEnabled: true # optional - default is true + lambdaSqsPermissionsEnabled: true # optional - default is true visibilityTimeout: 120 # optional (in seconds) - AWS default is 30 secs rawMessageDelivery: true # Optional - default value is true enabled: true # Optional - default value is true diff --git a/lib/__snapshots__/serverless-sns-sqs-lambda.test.ts.snap b/lib/__snapshots__/serverless-sns-sqs-lambda.test.ts.snap index 7902a4be..ac8f3cfa 100644 --- a/lib/__snapshots__/serverless-sns-sqs-lambda.test.ts.snap +++ b/lib/__snapshots__/serverless-sns-sqs-lambda.test.ts.snap @@ -2479,6 +2479,313 @@ Object { } `; +exports[`Test Serverless SNS SQS Lambda when the provider is specified via a command line option when queue policy is disabled should not produce IAM queue policy in the CF template 1`] = ` +Object { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "The AWS CloudFormation template for this Serverless application", + "Outputs": Object { + "ServerlessDeploymentBucketName": Object { + "Export": Object { + "Name": "sls-test-service-dev-test-ServerlessDeploymentBucketName", + }, + "Value": Object { + "Ref": "ServerlessDeploymentBucket", + }, + }, + "TestDashfunctionLambdaFunctionQualifiedArn": Object { + "Description": "Current Lambda function version", + "Export": Object { + "Name": "sls-test-service-dev-test-TestDashfunctionLambdaFunctionQualifiedArn", + }, + "Value": Object { + "Ref": "TestDashfunctionLambdaVersionA6M23sE6AN9SgN5IQgI9bd1tqh7YgxtybZ9LOhkLY4", + }, + }, + }, + "Resources": Object { + "IamRoleLambdaExecution": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": Object { + "Service": Array [ + "lambda.amazonaws.com", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Path": "/", + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Sub": "arn:\${AWS::Partition}:logs:\${AWS::Region}:\${AWS::AccountId}:log-group:/aws/lambda/test-service-dev-test*:*", + }, + ], + }, + Object { + "Action": Array [ + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Sub": "arn:\${AWS::Partition}:logs:\${AWS::Region}:\${AWS::AccountId}:log-group:/aws/lambda/test-service-dev-test*:*:*", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": Object { + "Fn::Join": Array [ + "-", + Array [ + "test-service", + "dev-test", + "lambda", + ], + ], + }, + }, + ], + "RoleName": Object { + "Fn::Join": Array [ + "-", + Array [ + "test-service", + "dev-test", + Object { + "Ref": "AWS::Region", + }, + "lambdaRole", + ], + ], + }, + }, + "Type": "AWS::IAM::Role", + }, + "ServerlessDeploymentBucket": Object { + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + }, + "Type": "AWS::S3::Bucket", + }, + "ServerlessDeploymentBucketPolicy": Object { + "Properties": Object { + "Bucket": Object { + "Ref": "ServerlessDeploymentBucket", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": false, + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":s3:::", + Object { + "Ref": "ServerlessDeploymentBucket", + }, + "/*", + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":s3:::", + Object { + "Ref": "ServerlessDeploymentBucket", + }, + ], + ], + }, + ], + }, + ], + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "Subscribesome-nameTopic": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "some-nameQueue", + "Arn", + ], + }, + "Protocol": "sqs", + "RawMessageDelivery": false, + "TopicArn": "arn:aws:sns:us-east-2:123456789012:MyTopic", + }, + "Type": "AWS::SNS::Subscription", + }, + "Test-functionEventSourceMappingSQSsome-nameQueue": Object { + "Properties": Object { + "BatchSize": 10, + "Enabled": "True", + "EventSourceArn": Object { + "Fn::GetAtt": Array [ + "some-nameQueue", + "Arn", + ], + }, + "FunctionName": Object { + "Fn::GetAtt": Array [ + "Test-functionLambdaFunction", + "Arn", + ], + }, + "MaximumBatchingWindowInSeconds": 0, + }, + "Type": "AWS::Lambda::EventSourceMapping", + }, + "TestDashfunctionLambdaFunction": Object { + "DependsOn": Array [ + "TestDashfunctionLogGroup", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "ServerlessDeploymentBucket", + }, + "S3Key": Any, + }, + "FunctionName": "test-service-dev-test-test-function", + "Handler": "handler.handler", + "MemorySize": 1024, + "Role": Object { + "Fn::GetAtt": Array [ + "IamRoleLambdaExecution", + "Arn", + ], + }, + "Runtime": "nodejs14.x", + "Timeout": 6, + }, + "Type": "AWS::Lambda::Function", + }, + "TestDashfunctionLambdaVersionA6M23sE6AN9SgN5IQgI9bd1tqh7YgxtybZ9LOhkLY4": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "CodeSha256": "gxQ2/ARVAXYSjz4OF5PnsOiOB+yUlXG8z5y5h6bNs7U=", + "FunctionName": Object { + "Ref": "TestDashfunctionLambdaFunction", + }, + }, + "Type": "AWS::Lambda::Version", + }, + "TestDashfunctionLogGroup": Object { + "Properties": Object { + "LogGroupName": "/aws/lambda/test-service-dev-test-test-function", + }, + "Type": "AWS::Logs::LogGroup", + }, + "some-nameDeadLetterQueue": Object { + "Properties": Object { + "QueueName": "test-service-dev-test-Test-functionsome-nameDeadLetterQueue", + }, + "Type": "AWS::SQS::Queue", + }, + "some-nameQueue": Object { + "Properties": Object { + "QueueName": "test-service-dev-test-Test-functionsome-nameQueue", + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "some-nameDeadLetterQueue", + "Arn", + ], + }, + "maxReceiveCount": 5, + }, + }, + "Type": "AWS::SQS::Queue", + }, + "some-nameQueuePolicy": Object { + "Properties": Object { + "PolicyDocument": Object { + "Id": "test-service-dev-test-Test-functionsome-nameQueue", + "Statement": Array [ + Object { + "Action": "SQS:SendMessage", + "Condition": Object { + "ArnEquals": Object { + "aws:SourceArn": Array [ + "arn:aws:sns:us-east-2:123456789012:MyTopic", + ], + }, + }, + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "some-nameQueue", + "Arn", + ], + }, + "Sid": "test-service-dev-test-Test-functionsome-nameSid", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "some-nameQueue", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + }, +} +`; + exports[`Test Serverless SNS SQS Lambda when the provider is specified via a config option in serverless.yml when fifo is true should produce valid fifo queues 1`] = ` Object { "Resources": Object { diff --git a/lib/serverless-sns-sqs-lambda.test.ts b/lib/serverless-sns-sqs-lambda.test.ts index 5460f4f2..6131974d 100644 --- a/lib/serverless-sns-sqs-lambda.test.ts +++ b/lib/serverless-sns-sqs-lambda.test.ts @@ -242,6 +242,7 @@ describe("Test Serverless SNS SQS Lambda", () => { kmsDataKeyReusePeriodSeconds: 200, deadLetterMessageRetentionPeriodSeconds: 1209600, deadLetterQueueEnabled: true, + lambdaSqsPermissionsEnabled: true, enabled: false, visibilityTimeout: 999, rawMessageDelivery: true, @@ -301,6 +302,41 @@ describe("Test Serverless SNS SQS Lambda", () => { }); }); + describe("when queue policy is disabled", () => { + it("should not produce IAM queue policy in the CF template", async () => { + const { cfTemplate } = await runServerless(serverlessPath, { + command: "package", + config: { + ...baseConfig, + functions: { + ["test-function"]: { + handler: "handler.handler", + events: [ + { + snsSqs: { + name: "some-name", + topicArn: "arn:aws:sns:us-east-2:123456789012:MyTopic", + lambdaSqsPermissionsEnabled: false + } + } + ] + } + } + } + }); + + expect(cfTemplate).toMatchSnapshot({ + Resources: { + TestDashfunctionLambdaFunction: { + Properties: { + Code: { S3Key: expect.any(String) } + } + } + } + }); + }); + }); + describe("when encryption parameters are not provided", () => { it("should produce valid SQS CF template items", async () => { const { cfTemplate } = await runServerless(serverlessPath, { diff --git a/lib/serverless-sns-sqs-lambda.ts b/lib/serverless-sns-sqs-lambda.ts index ca35dcef..dc6591bc 100644 --- a/lib/serverless-sns-sqs-lambda.ts +++ b/lib/serverless-sns-sqs-lambda.ts @@ -27,6 +27,7 @@ type Config = { deadLetterMessageRetentionPeriodSeconds: number; deadLetterQueueEnabled: boolean; enabled: boolean; + lambdaSqsPermissionsEnabled: boolean; fifo: boolean; visibilityTimeout: number; rawMessageDelivery: boolean; @@ -131,6 +132,7 @@ const addResource = ( * kmsDataKeyReusePeriodSeconds: 600 * deadLetterMessageRetentionPeriodSeconds: 1209600 * deadLetterQueueEnabled: true + * lambdaSqsPermissionsEnabled: true * visibilityTimeout: 120 * rawMessageDelivery: true * enabled: false @@ -200,6 +202,7 @@ export default class ServerlessSnsSqsLambda { maximum: 1209600 }, deadLetterQueueEnabled: { type: "boolean" }, + lambdaSqsPermissionsEnabled: { type: "boolean" }, rawMessageDelivery: { type: "boolean" }, enabled: { type: "boolean" }, fifo: { type: "boolean" }, @@ -315,6 +318,7 @@ Usage kmsDataKeyReusePeriodSeconds: 600 # optional - AWS default is 300 seconds deadLetterMessageRetentionPeriodSeconds: 1209600 # optional - AWS default is 345600 secs (4 days) deadLetterQueueEnabled: true # optional - default is enabled + lambdaSqsPermissionsEnabled: true # optional - default is enabled enabled: true # optional - AWS default is true fifo: false # optional - AWS default is false visibilityTimeout: 30 # optional - AWS default is 30 seconds @@ -362,6 +366,10 @@ Usage config.deadLetterQueueEnabled !== undefined ? config.deadLetterQueueEnabled : true, + lambdaSqsPermissionsEnabled: + config.lambdaSqsPermissionsEnabled !== undefined + ? config.lambdaSqsPermissionsEnabled + : true, enabled: config.enabled, fifo: config.fifo !== undefined ? config.fifo : false, visibilityTimeout: config.visibilityTimeout, @@ -600,7 +608,12 @@ Usage */ addLambdaSqsPermissions( template, - { name, kmsMasterKeyId, deadLetterQueueEnabled } + { + name, + kmsMasterKeyId, + deadLetterQueueEnabled, + lambdaSqsPermissionsEnabled + } ) { if (template.Resources.IamRoleLambdaExecution === undefined) { // The user has set their own custom role ARN so the Serverless generated role is not generated @@ -608,6 +621,10 @@ Usage // this the relevant policy to allow the lambda to access the queue. return; } + if (!lambdaSqsPermissionsEnabled) { + // The user wants to use their own IAM policy and does not want plugin to automatically append to the default one. + return; + } const queues = [{ "Fn::GetAtt": [`${name}Queue`, "Arn"] }]; if (deadLetterQueueEnabled) { queues.push({ "Fn::GetAtt": [`${name}DeadLetterQueue`, "Arn"] });