diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/SqsTestDefaultTestDeployAssert659366A6.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/SqsTestDefaultTestDeployAssert659366A6.assets.json new file mode 100644 index 0000000000000..6d7ba276e8984 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/SqsTestDefaultTestDeployAssert659366A6.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "SqsTestDefaultTestDeployAssert659366A6.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/SqsTestDefaultTestDeployAssert659366A6.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/SqsTestDefaultTestDeployAssert659366A6.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/SqsTestDefaultTestDeployAssert659366A6.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/aws-cdk-sqs.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/aws-cdk-sqs.assets.json new file mode 100644 index 0000000000000..588e3fca7e14a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/aws-cdk-sqs.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "d72236aa3f4906a9f550a7a28798879c3ad8fe6a6d47dd38a1d3e3565256f769": { + "source": { + "path": "aws-cdk-sqs.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d72236aa3f4906a9f550a7a28798879c3ad8fe6a6d47dd38a1d3e3565256f769.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/aws-cdk-sqs.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/aws-cdk-sqs.template.json new file mode 100644 index 0000000000000..a116bcb48f618 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/aws-cdk-sqs.template.json @@ -0,0 +1,72 @@ +{ + "Resources": { + "SourceQueue1F4BBA4BB": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SourceQueue22481CB5A": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "DeadLetterQueue9F481546": { + "Type": "AWS::SQS::Queue", + "Properties": { + "RedriveAllowPolicy": { + "redrivePermission": "byQueue", + "sourceQueueArns": [ + { + "Fn::GetAtt": [ + "SourceQueue1F4BBA4BB", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "SourceQueue22481CB5A", + "Arn" + ] + } + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/integ.json new file mode 100644 index 0000000000000..544e3721ce789 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "SqsTest/DefaultTest": { + "stacks": [ + "aws-cdk-sqs" + ], + "assertionStack": "SqsTest/DefaultTest/DeployAssert", + "assertionStackName": "SqsTestDefaultTestDeployAssert659366A6" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/manifest.json new file mode 100644 index 0000000000000..86395236fd481 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/manifest.json @@ -0,0 +1,125 @@ +{ + "version": "36.0.0", + "artifacts": { + "aws-cdk-sqs.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-sqs.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-sqs": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-sqs.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d72236aa3f4906a9f550a7a28798879c3ad8fe6a6d47dd38a1d3e3565256f769.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-sqs.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-sqs.assets" + ], + "metadata": { + "/aws-cdk-sqs/SourceQueue1/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceQueue1F4BBA4BB" + } + ], + "/aws-cdk-sqs/SourceQueue2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceQueue22481CB5A" + } + ], + "/aws-cdk-sqs/DeadLetterQueue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DeadLetterQueue9F481546" + } + ], + "/aws-cdk-sqs/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-sqs/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-sqs" + }, + "SqsTestDefaultTestDeployAssert659366A6.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "SqsTestDefaultTestDeployAssert659366A6.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "SqsTestDefaultTestDeployAssert659366A6": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "SqsTestDefaultTestDeployAssert659366A6.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "SqsTestDefaultTestDeployAssert659366A6.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "SqsTestDefaultTestDeployAssert659366A6.assets" + ], + "metadata": { + "/SqsTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/SqsTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "SqsTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/tree.json new file mode 100644 index 0000000000000..3cf1e989a0a30 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.js.snapshot/tree.json @@ -0,0 +1,185 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-cdk-sqs": { + "id": "aws-cdk-sqs", + "path": "aws-cdk-sqs", + "children": { + "SourceQueue1": { + "id": "SourceQueue1", + "path": "aws-cdk-sqs/SourceQueue1", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-sqs/SourceQueue1/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.CfnQueue", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.Queue", + "version": "0.0.0" + } + }, + "SourceQueue2": { + "id": "SourceQueue2", + "path": "aws-cdk-sqs/SourceQueue2", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-sqs/SourceQueue2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.CfnQueue", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.Queue", + "version": "0.0.0" + } + }, + "DeadLetterQueue": { + "id": "DeadLetterQueue", + "path": "aws-cdk-sqs/DeadLetterQueue", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-sqs/DeadLetterQueue/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": { + "redriveAllowPolicy": { + "redrivePermission": "byQueue", + "sourceQueueArns": [ + { + "Fn::GetAtt": [ + "SourceQueue1F4BBA4BB", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "SourceQueue22481CB5A", + "Arn" + ] + } + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.CfnQueue", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.Queue", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-sqs/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-sqs/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "SqsTest": { + "id": "SqsTest", + "path": "SqsTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "SqsTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "SqsTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "SqsTest/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "SqsTest/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "SqsTest/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.ts new file mode 100644 index 0000000000000..e6079ba21f67e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sqs/test/integ.sqs-source-queue-permission.ts @@ -0,0 +1,24 @@ + +import { App, Stack } from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import { Queue, RedrivePermission } from 'aws-cdk-lib/aws-sqs'; + +const app = new App(); + +const stack = new Stack(app, 'aws-cdk-sqs'); + +const sourceQueue1 = new Queue(stack, 'SourceQueue1'); +const sourceQueue2 = new Queue(stack, 'SourceQueue2'); + +new Queue(stack, 'DeadLetterQueue', { + redriveAllowPolicy: { + sourceQueues: [sourceQueue1, sourceQueue2], + redrivePermission: RedrivePermission.BY_QUEUE, + }, +}); + +new integ.IntegTest(app, 'SqsTest', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/aws-cdk-lib/aws-sqs/README.md b/packages/aws-cdk-lib/aws-sqs/README.md index d910cd3d96b80..b7f4f8e409972 100644 --- a/packages/aws-cdk-lib/aws-sqs/README.md +++ b/packages/aws-cdk-lib/aws-sqs/README.md @@ -84,3 +84,27 @@ the SQS manual. Note that FIFO queues are not available in all AWS regions. A queue can be made a FIFO queue by either setting `fifo: true`, giving it a name which ends in `".fifo"`, or by enabling a FIFO specific feature such as: content-based deduplication, deduplication scope or fifo throughput limit. + +## Dead letter source queues permission + +You can configure the permission settings for queues that can designate the created queue as their dead-letter queue using the `redriveAllowPolicy` attribute. + +By default, all queues within the same account and region are permitted as source queues. + +```ts +declare const sourceQueue: sqs.IQueue; + +// Only the sourceQueue can specify this queue as the dead-letter queue. +const queue1 = new sqs.Queue(this, 'Queue2', { + redriveAllowPolicy: { + sourceQueues: [sourceQueue], + } +}); + +// No source queues can specify this queue as the dead-letter queue. +const queue2 = new sqs.Queue(this, 'Queue', { + redriveAllowPolicy: { + redrivePermission: sqs.RedrivePermission.DENY_ALL, + } +}); +``` diff --git a/packages/aws-cdk-lib/aws-sqs/lib/queue.ts b/packages/aws-cdk-lib/aws-sqs/lib/queue.ts index a122045571937..be742bf64c045 100644 --- a/packages/aws-cdk-lib/aws-sqs/lib/queue.ts +++ b/packages/aws-cdk-lib/aws-sqs/lib/queue.ts @@ -178,6 +178,14 @@ export interface QueueProps { * @default false */ readonly enforceSSL?: boolean; + + /** + * The string that includes the parameters for the permissions for the dead-letter queue + * redrive permission and which source queues can specify dead-letter queues. + * + * @default - All source queues can designate this queue as their dead-letter queue. + */ + readonly redriveAllowPolicy?: RedriveAllowPolicy; } /** @@ -195,6 +203,34 @@ export interface DeadLetterQueue { readonly maxReceiveCount: number; } +/** + * Permission settings for the dead letter source queue + */ +export interface RedriveAllowPolicy { + /** + * Permission settings for source queues that can designate this queue as their dead-letter queue. + * + * @default - `RedrivePermission.BY_QUEUE` if `sourceQueues` is specified,`RedrivePermission.ALLOW_ALL` otherwise. + */ + readonly redrivePermission?: RedrivePermission; + + /** + * Source queues that can designate this queue as their dead-letter queue. + * + * When `redrivePermission` is set to `RedrivePermission.BY_QUEUE`, this parameter is required. + * + * You can specify up to 10 source queues. + * To allow more than 10 source queues to specify dead-letter queues, set the `redrivePermission` to + * `RedrivePermission.ALLOW_ALL`. + * + * When `redrivePermission` is either `RedrivePermission.ALLOW_ALL` or `RedrivePermission.DENY_ALL`, + * this parameter cannot be set. + * + * @default - Required when `redrivePermission` is `RedrivePermission.BY_QUEUE`, cannot be defined otherwise. + */ + readonly sourceQueues?: IQueue[]; +} + /** * What kind of deduplication scope to apply */ @@ -223,6 +259,24 @@ export enum FifoThroughputLimit { PER_MESSAGE_GROUP_ID = 'perMessageGroupId', } +/** + * The permission type that defines which source queues can specify the current queue as the dead-letter queue + */ +export enum RedrivePermission { + /** + * Any source queues in this AWS account in the same Region can specify this queue as the dead-letter queue + */ + ALLOW_ALL = 'allowAll', + /** + * No source queues can specify this queue as the dead-letter queue + */ + DENY_ALL = 'denyAll', + /** + * Only queues specified by the `sourceQueueArns` parameter can specify this queue as the dead-letter queue + */ + BY_QUEUE = 'byQueue', +} + /** * A new Amazon SQS queue */ @@ -331,6 +385,20 @@ export class Queue extends QueueBase { validateProps(props); + if (props.redriveAllowPolicy) { + const { redrivePermission, sourceQueues } = props.redriveAllowPolicy; + if (redrivePermission === RedrivePermission.BY_QUEUE) { + if (!sourceQueues || sourceQueues.length === 0) { + throw new Error('At least one source queue must be specified when RedrivePermission is set to \'byQueue\''); + } + if (sourceQueues && sourceQueues.length > 10) { + throw new Error('Up to 10 sourceQueues can be specified. Set RedrivePermission to \'allowAll\' to specify more'); + } + } else if (redrivePermission && sourceQueues) { + throw new Error('sourceQueues cannot be configured when RedrivePermission is set to \'allowAll\' or \'denyAll\''); + } + } + const redrivePolicy = props.deadLetterQueue ? { deadLetterTargetArn: props.deadLetterQueue.queue.queueArn, @@ -338,6 +406,15 @@ export class Queue extends QueueBase { } : undefined; + // When `redriveAllowPolicy` is provided, `redrivePermission` defaults to allow all queues (`ALLOW_ALL`); + const redriveAllowPolicy = props.redriveAllowPolicy ? { + redrivePermission: props.redriveAllowPolicy.redrivePermission + // When `sourceQueues` is provided in `redriveAllowPolicy`, `redrivePermission` defaults to allow specified queues (`BY_QUEUE`); + // otherwise, it defaults to allow all queues (`ALLOW_ALL`). + ?? props.redriveAllowPolicy.sourceQueues ? RedrivePermission.BY_QUEUE : RedrivePermission.ALLOW_ALL, + sourceQueueArns: props.redriveAllowPolicy.sourceQueues?.map(q => q.queueArn), + } : undefined; + const { encryptionMasterKey, encryptionProps, encryptionType } = _determineEncryptionProps.call(this); const fifoProps = this.determineFifoProps(props); @@ -348,6 +425,7 @@ export class Queue extends QueueBase { ...fifoProps, ...encryptionProps, redrivePolicy, + redriveAllowPolicy, delaySeconds: props.deliveryDelay && props.deliveryDelay.toSeconds(), maximumMessageSize: props.maxMessageSizeBytes, messageRetentionPeriod: props.retentionPeriod && props.retentionPeriod.toSeconds(), diff --git a/packages/aws-cdk-lib/aws-sqs/test/sqs.test.ts b/packages/aws-cdk-lib/aws-sqs/test/sqs.test.ts index 0c0492b26f28b..956c8a77cdd7a 100644 --- a/packages/aws-cdk-lib/aws-sqs/test/sqs.test.ts +++ b/packages/aws-cdk-lib/aws-sqs/test/sqs.test.ts @@ -730,6 +730,128 @@ test('fails if queue policy has no IAM principals', () => { expect(() => app.synth()).toThrow(/A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); }); +describe('redriveAllowPolicy', () => { + test('Default settings for the dead letter source queue permission', () => { + const stack = new Stack(); + new sqs.Queue(stack, 'Queue', { + redriveAllowPolicy: {}, + }); + + Template.fromStack(stack).templateMatches({ + 'Resources': { + 'Queue4A7E3555': { + 'Type': 'AWS::SQS::Queue', + 'Properties': { + 'RedriveAllowPolicy': { + 'redrivePermission': 'allowAll', + }, + }, + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + }, + }); + }); + + test('explicit specification of dead letter source queues', () => { + const stack = new Stack(); + const sourceQueue1 = new sqs.Queue(stack, 'SourceQueue1'); + const sourceQueue2 = new sqs.Queue(stack, 'SourceQueue2'); + new sqs.Queue(stack, 'Queue', { redriveAllowPolicy: { sourceQueues: [sourceQueue1, sourceQueue2] } }); + + Template.fromStack(stack).templateMatches({ + 'Resources': { + 'SourceQueue1F4BBA4BB': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'SourceQueue22481CB5A': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'Queue4A7E3555': { + 'Type': 'AWS::SQS::Queue', + 'Properties': { + 'RedriveAllowPolicy': { + 'redrivePermission': 'byQueue', + 'sourceQueueArns': [ + { + 'Fn::GetAtt': [ + 'SourceQueue1F4BBA4BB', + 'Arn', + ], + }, + { + 'Fn::GetAtt': [ + 'SourceQueue22481CB5A', + 'Arn', + ], + }, + ], + }, + }, + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + }, + }); + }); + + test('throw if sourceQueues is not specified when redrivePermission is byQueue', () => { + const stack = new Stack(); + expect(() => { + new sqs.Queue(stack, 'Queue', { + redriveAllowPolicy: { + redrivePermission: sqs.RedrivePermission.BY_QUEUE, + }, + }); + }).toThrow(/At least one source queue must be specified when RedrivePermission is set to 'byQueue'/); + }); + + test('throw if dead letter source queues are specified with allowAll permission', () => { + const stack = new Stack(); + const sourceQueue1 = new sqs.Queue(stack, 'SourceQueue1'); + expect(() => { + new sqs.Queue(stack, 'Queue', { + redriveAllowPolicy: { + sourceQueues: [sourceQueue1], + redrivePermission: sqs.RedrivePermission.ALLOW_ALL, + }, + }); + }).toThrow(/sourceQueues cannot be configured when RedrivePermission is set to 'allowAll' or 'denyAll'/); + }); + + test('throw if souceQueues length is greater than 10', () => { + const stack = new Stack(); + const sourceQueues: sqs.IQueue[] = []; + for (let i = 0; i < 11; i++) { + sourceQueues.push(new sqs.Queue(stack, `SourceQueue${i}`)); + } + expect(() => { + new sqs.Queue(stack, 'Queue', { + redriveAllowPolicy: { + sourceQueues, + redrivePermission: sqs.RedrivePermission.BY_QUEUE, + }, + }); + }).toThrow(/Up to 10 sourceQueues can be specified. Set RedrivePermission to 'allowAll' to specify more/); + }); + + test('throw if sourceQueues is blank array when redrivePermission is byQueue', () => { + const stack = new Stack(); + expect(() => { + new sqs.Queue(stack, 'Queue', { + redriveAllowPolicy: { + sourceQueues: [], + redrivePermission: sqs.RedrivePermission.BY_QUEUE, + }, + }); + }).toThrow(/At least one source queue must be specified when RedrivePermission is set to 'byQueue'/); + }); +}); + function testGrant(action: (q: sqs.Queue, principal: iam.IPrincipal) => void, ...expectedActions: string[]) { const stack = new Stack(); const queue = new sqs.Queue(stack, 'MyQueue');