diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.eslintignore @@ -0,0 +1,5 @@ +lib/*.js +test/*.js +*.d.ts +coverage +test/lambda/index.js \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.gitignore b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.npmignore b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/.npmignore @@ -0,0 +1,21 @@ +# Exclude typescript source and config +*.ts +tsconfig.json +coverage +.nyc_output +*.tgz +*.snk +*.tsbuildinfo + +# Include javascript files and typescript declarations +!*.js +!*.d.ts + +# Exclude jsii outdir +dist + +# Include .jsii +!.jsii + +# Include .jsii +!.jsii \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/README.md new file mode 100644 index 000000000..41b48c6a2 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/README.md @@ -0,0 +1,93 @@ +# aws-sns-sqs module + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> All classes are under active development and subject to non-backward compatible changes or removal in any +> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. +> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| +|:-------------|:-------------| +
+ +| **Language** | **Package** | +|:-------------|-----------------| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_sns_sqs`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-sns-sqs`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.snssqs`| + +This AWS Solutions Construct implements an Amazon SNS topic connected to an Amazon SQS queue. + +Here is a minimal deployable pattern definition: + +``` javascript +const { SnsToSqs } = require('@aws-solutions-constructs/aws-sns-sqs'); + +const props: SnsToSqsProps = {}; + +new SnsToSqs(stack, 'SnsToSqsPattern', props); + +``` + +## Initializer + +``` text +new SnsToSqs(scope: Construct, id: string, props: SnsToSqsProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`SnsToSqsProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|existingTopicObj?|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|An optional, existing SNS topic to be used instead of the default topic. If an existing topic is provided, the `topicProps` property will be ignored.| +|topicProps?|[`sns.TopicProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.TopicProps.html)|Optional user provided properties to override the default properties for the SNS topic.| +|existingQueueObj?|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|An optional, existing SQS queue to be used instead of the default queue. If an existing queue is provided, the `queueProps` property will be ignored.| +|queueProps?|[`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html)|Optional user provided properties to override the default properties for the SQS queue.| +|deployDeadLetterQueue?|`boolean`|Whether to create a secondary queue to be used as a dead letter queue. Defaults to true.| +|deadLetterQueueProps?|[`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html)|Optional user-provided props to override the default props for the dead letter SQS queue.| +|maxReceiveCount?|`number`|The number of times a message can be unsuccessfully dequeued before being moved to the dead letter queue. Defaults to 15.| +|enableEncryptionWithCustomerManagedKey?|`boolean`|Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in the encryptionKey property for this construct.| +|encryptionKey?|[`kms.Key`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.Key.html)|An optional, imported encryption key to encrypt the SQS queue, and SNS Topic.| +|encryptionKeyProps?|[`kms.KeyProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.KeyProps.html)|An optional, user provided properties to override the default properties for the KMS encryption key.| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|snsTopic|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|Returns an instance of the SNS topic created by the pattern.| +|encryptionKey|[`kms.Key`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.Key.html)|Returns an instance of kms.Key used for the SQS queue, and SNS Topic.| +|sqsQueue|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of the SQS queue created by the pattern.| +|deadLetterQueue?|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of the dead-letter SQS queue created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon SNS Topic +* Configure least privilege access permissions for SNS Topic +* Enable server-side encryption for SNS Topic using Customer managed KMS Key +* Enforce encryption of data in transit + +### Amazon SQS Queue +* Configure least privilege access permissions for SQS Queue +* Deploy SQS dead-letter queue for the source SQS Queue +* Enable server-side encryption for SQS Queue using Customer managed KMS Key +* Enforce encryption of data in transit + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/architecture.png b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/architecture.png new file mode 100644 index 000000000..0d8e77d9a Binary files /dev/null and b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/architecture.png differ diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/lib/index.ts new file mode 100644 index 000000000..af39074ce --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/lib/index.ts @@ -0,0 +1,168 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import * as sqs from '@aws-cdk/aws-sqs'; +import * as sns from '@aws-cdk/aws-sns'; +import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; +import * as defaults from '@aws-solutions-constructs/core'; +import { Construct } from '@aws-cdk/core'; +import {buildEncryptionKey} from "@aws-solutions-constructs/core"; + +/** + * @summary The properties for the SnsToSqs class. + */ +export interface SnsToSqsProps { + /** + * Existing instance of SNS topic object, if this is set then topicProps is ignored. + * + * @default - Default props are used + */ + readonly existingTopicObj?: sns.Topic, + /** + * Optional user provided properties to override the default properties for the SNS topic. + * + * @default - Default properties are used. + */ + readonly topicProps?: sns.TopicProps, + /** + * Existing instance of SQS queue object, if this is set then queueProps is ignored. + * + * @default - Default props are used + */ + readonly existingQueueObj?: sqs.Queue, + /** + * Optional user provided properties + * + * @default - Default props are used + */ + readonly queueProps?: sqs.QueueProps, + /** + * Optional user provided properties for the dead letter queue + * + * @default - Default props are used + */ + readonly deadLetterQueueProps?: sqs.QueueProps, + /** + * Whether to deploy a secondary queue to be used as a dead letter queue. + * + * @default - true. + */ + readonly deployDeadLetterQueue?: boolean, + /** + * The number of times a message can be unsuccessfully dequeued before being moved to the dead-letter queue. + * + * @default - required field if deployDeadLetterQueue=true. + */ + readonly maxReceiveCount?: number + /** + * Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in + * the encryptionKey property for this construct. + * + * @default - true (encryption enabled, managed by this CDK app). + */ + readonly enableEncryptionWithCustomerManagedKey?: boolean + /** + * An optional, imported encryption key to encrypt the SQS queue, and SNS Topic. + * + * @default - not specified. + */ + readonly encryptionKey?: kms.Key + /** + * Optional user-provided props to override the default props for the encryption key. + * + * @default - Default props are used. + */ + readonly encryptionKeyProps?: kms.KeyProps +} + +/** + * @summary The SnsToSqs class. + */ +export class SnsToSqs extends Construct { + public readonly snsTopic: sns.Topic; + public readonly encryptionKey?: kms.Key; + public readonly sqsQueue: sqs.Queue; + public readonly deadLetterQueue?: sqs.DeadLetterQueue; + + /** + * @summary Constructs a new instance of the SnsToSqs class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {SnsToSqsProps} props - user provided props for the construct. + * @since 1.61.0 + * @access public + */ + constructor(scope: Construct, id: string, props: SnsToSqsProps) { + super(scope, id); + + // Setup the dead letter queue, if applicable + if (props.deployDeadLetterQueue || props.deployDeadLetterQueue === undefined) { + const dlq: sqs.Queue = defaults.buildQueue(this, 'deadLetterQueue', { + queueProps: props.deadLetterQueueProps + }); + this.deadLetterQueue = defaults.buildDeadLetterQueue({ + deadLetterQueue: dlq, + maxReceiveCount: props.maxReceiveCount + }); + } + + let enableEncryptionParam:boolean | undefined = props.enableEncryptionWithCustomerManagedKey; + let encryptionKeyParam:kms.Key | undefined = props.encryptionKey; + + if (props.enableEncryptionWithCustomerManagedKey === undefined || + props.enableEncryptionWithCustomerManagedKey === true) { + enableEncryptionParam = true; + // Create the encryptionKey if none was provided + if (!props.encryptionKey) { + encryptionKeyParam = buildEncryptionKey(scope, { + encryptionKeyProps: props.encryptionKeyProps + }); + } + } + // Setup the SNS topic + if (!props.existingTopicObj) { + // If an existingTopicObj was not specified create new topic + [this.snsTopic, this.encryptionKey] = defaults.buildTopic(this, { + topicProps: props.topicProps, + enableEncryption: enableEncryptionParam, + encryptionKey: encryptionKeyParam + }); + } else { + // If an existingTopicObj was specified utilize the provided topic + this.snsTopic = props.existingTopicObj; + } + + // Setup the queue + this.sqsQueue = defaults.buildQueue(this, 'queue', { + existingQueueObj: props.existingQueueObj, + queueProps: props.queueProps, + deadLetterQueue: this.deadLetterQueue, + enableEncryption: enableEncryptionParam, + encryptionKey: encryptionKeyParam + }); + + // Setup the SQS queue subscription to the SNS topic + this.snsTopic.addSubscription(new subscriptions.SqsSubscription(this.sqsQueue)); + + // Grant SNS service access to the SQS queue encryption key + if (this.sqsQueue.encryptionMasterKey) { + this.sqsQueue.encryptionMasterKey.grant(new iam.ServicePrincipal("sns.amazonaws.com"), + 'kms:Decrypt', + 'kms:GenerateDataKey*', + ); + } + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/package.json b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/package.json new file mode 100644 index 000000000..f79f4484d --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/package.json @@ -0,0 +1,85 @@ +{ + "name": "@aws-solutions-constructs/aws-sns-sqs", + "version": "1.61.0", + "description": "CDK constructs for defining an interaction between an Amazon SNS topic and an Amazon SQS queue.", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-sns-sqs" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-assert": "cdk-integ-assert", + "integ-no-clean": "cdk-integ --no-clean", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.snssqs", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "snssqs" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.SnsSqs", + "packageId": "Amazon.Constructs.AWS.SnsSqs", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-sns-sqs", + "module": "aws_solutions_constructs.aws_sns_sqs" + } + } + }, + "dependencies": { + "@aws-cdk/aws-iam": "~1.61.0", + "@aws-cdk/aws-sns": "~1.61.0", + "@aws-cdk/aws-sqs": "~1.61.0", + "@aws-cdk/aws-sns-subscriptions": "~1.61.0", + "@aws-cdk/aws-kms": "~1.61.0", + "@aws-cdk/core": "~1.61.0", + "@aws-solutions-constructs/core": "~1.61.0", + "constructs": "^3.0.4" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.61.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-iam": "~1.61.0", + "@aws-cdk/aws-sns": "~1.61.0", + "@aws-cdk/aws-sqs": "~1.61.0", + "@aws-cdk/aws-sns-subscriptions": "~1.61.0", + "@aws-cdk/aws-kms": "~1.61.0", + "@aws-cdk/core": "~1.61.0", + "@aws-solutions-constructs/core": "~1.61.0", + "constructs": "^3.0.4" + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/__snapshots__/sns-sqs.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/__snapshots__/sns-sqs.test.js.snap new file mode 100644 index 000000000..cf07f6261 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/__snapshots__/sns-sqs.test.js.snap @@ -0,0 +1,1272 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pattern deployment w/ new Topic, new Queue and default props 1`] = ` +Object { + "Resources": Object { + "EncryptionKey1B843E66": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "EnableKeyRotation": true, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "testsnssqsSnsTopic2CD0065B": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Ref": "EncryptionKey1B843E66", + }, + }, + "Type": "AWS::SNS::Topic", + }, + "testsnssqsSnsTopicPolicy604079F2": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe", + ], + "Condition": Object { + "StringEquals": Object { + "AWS:SourceOwner": Object { + "Ref": "AWS::AccountId", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Ref": "testsnssqsSnsTopic2CD0065B", + }, + "Sid": "TopicOwnerOnlyAccess", + }, + Object { + "Action": Array [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe", + ], + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Ref": "testsnssqsSnsTopic2CD0065B", + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Topics": Array [ + Object { + "Ref": "testsnssqsSnsTopic2CD0065B", + }, + ], + }, + "Type": "AWS::SNS::TopicPolicy", + }, + "testsnssqsdeadLetterQueue8DACC0A1": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, + "testsnssqsdeadLetterQueuePolicyAB8A9883": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "testsnssqsdeadLetterQueue8DACC0A1", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "testsnssqsqueueB02504BF": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn", + ], + }, + "maxReceiveCount": 15, + }, + }, + "Type": "AWS::SQS::Queue", + }, + "testsnssqsqueuePolicyE64464B6": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "testsnssqsqueueB02504BF", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "testsnssqsqueueB02504BF", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + Object { + "Action": "sqs:SendMessage", + "Condition": Object { + "ArnEquals": Object { + "aws:SourceArn": Object { + "Ref": "testsnssqsSnsTopic2CD0065B", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "testsnssqsqueueB02504BF", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "testsnssqsqueueB02504BF", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "testsnssqsqueuetestsnssqsSnsTopicE16CFFE0983CE231": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "testsnssqsqueueB02504BF", + "Arn", + ], + }, + "Protocol": "sqs", + "TopicArn": Object { + "Ref": "testsnssqsSnsTopic2CD0065B", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + }, +} +`; + +exports[`Test deployment w/ existing queue, and topic 1`] = ` +Object { + "Resources": Object { + "EncryptionKey1B843E66": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "EnableKeyRotation": true, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "existingqueueobjF8AF0ED1": Object { + "Properties": Object { + "QueueName": "existing-queue-obj", + }, + "Type": "AWS::SQS::Queue", + }, + "existingqueueobjPolicy847305AE": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sqs:SendMessage", + "Condition": Object { + "ArnEquals": Object { + "aws:SourceArn": Object { + "Ref": "existingtopicobjF4A24735", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "existingqueueobjF8AF0ED1", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "existingqueueobjF8AF0ED1", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "existingqueueobjexistingtopicobjF03E40E2": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "existingqueueobjF8AF0ED1", + "Arn", + ], + }, + "Protocol": "sqs", + "TopicArn": Object { + "Ref": "existingtopicobjF4A24735", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + "existingtopicobjF4A24735": Object { + "Properties": Object { + "TopicName": "existing-topic-obj", + }, + "Type": "AWS::SNS::Topic", + }, + "snstosqsstackdeadLetterQueueA02EB1B1": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, + "snstosqsstackdeadLetterQueuePolicy4E639DF8": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "snstosqsstackdeadLetterQueueA02EB1B1", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + }, +} +`; + +exports[`Test deployment with SNS managed KMS key 1`] = ` +Object { + "Resources": Object { + "snstosqsstackSnsTopicB387685B": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sns", + }, + "Type": "AWS::SNS::Topic", + }, + "snstosqsstackSnsTopicPolicy824AEFAD": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe", + ], + "Condition": Object { + "StringEquals": Object { + "AWS:SourceOwner": Object { + "Ref": "AWS::AccountId", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + "Sid": "TopicOwnerOnlyAccess", + }, + Object { + "Action": Array [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe", + ], + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Topics": Array [ + Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + ], + }, + "Type": "AWS::SNS::TopicPolicy", + }, + "snstosqsstackdeadLetterQueueA02EB1B1": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, + "snstosqsstackdeadLetterQueuePolicy4E639DF8": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "snstosqsstackdeadLetterQueueA02EB1B1", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "snstosqsstackqueue262BCE03": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueueKey743636E7", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "maxReceiveCount": 15, + }, + }, + "Type": "AWS::SQS::Queue", + }, + "snstosqsstackqueueKey743636E7": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Description": "Created by Default/sns-to-sqs-stack/queue", + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "snstosqsstackqueuePolicy4A9E8A77": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + Object { + "Action": "sqs:SendMessage", + "Condition": Object { + "ArnEquals": Object { + "aws:SourceArn": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "snstosqsstackqueue262BCE03", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "snstosqsstackqueuesnstosqsstackSnsTopic1DC3C73AEA256098": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + "Protocol": "sqs", + "TopicArn": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + }, +} +`; + +exports[`Test deployment with imported encryption key 1`] = ` +Object { + "Resources": Object { + "importedkey38675D68": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "EnableKeyRotation": false, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "snstosqsstackSnsTopicB387685B": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Ref": "importedkey38675D68", + }, + }, + "Type": "AWS::SNS::Topic", + }, + "snstosqsstackSnsTopicPolicy824AEFAD": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe", + ], + "Condition": Object { + "StringEquals": Object { + "AWS:SourceOwner": Object { + "Ref": "AWS::AccountId", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + "Sid": "TopicOwnerOnlyAccess", + }, + Object { + "Action": Array [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe", + ], + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Topics": Array [ + Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + ], + }, + "Type": "AWS::SNS::TopicPolicy", + }, + "snstosqsstackdeadLetterQueueA02EB1B1": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, + "snstosqsstackdeadLetterQueuePolicy4E639DF8": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "snstosqsstackdeadLetterQueueA02EB1B1", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "snstosqsstackqueue262BCE03": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "importedkey38675D68", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "snstosqsstackdeadLetterQueueA02EB1B1", + "Arn", + ], + }, + "maxReceiveCount": 15, + }, + }, + "Type": "AWS::SQS::Queue", + }, + "snstosqsstackqueuePolicy4A9E8A77": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + Object { + "Action": "sqs:SendMessage", + "Condition": Object { + "ArnEquals": Object { + "aws:SourceArn": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Service": "sns.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "snstosqsstackqueue262BCE03", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "snstosqsstackqueuesnstosqsstackSnsTopic1DC3C73AEA256098": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "snstosqsstackqueue262BCE03", + "Arn", + ], + }, + "Protocol": "sqs", + "TopicArn": Object { + "Ref": "snstosqsstackSnsTopicB387685B", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.existing-kms-key.expected.json b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.existing-kms-key.expected.json new file mode 100644 index 000000000..e4ef2a160 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.existing-kms-key.expected.json @@ -0,0 +1,357 @@ +{ + "Description": "Integration Test for aws-sns-sqs with existing KMS key", + "Resources": { + "ImportedEncryptionKeyBE10B2FC": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "EnableKeyRotation": true + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testsnssqsdeadLetterQueue8DACC0A1": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": "alias/aws/sqs" + } + }, + "testsnssqsdeadLetterQueuePolicyAB8A9883": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testsnssqsdeadLetterQueue8DACC0A1" + } + ] + } + }, + "testsnssqsSnsTopic2CD0065B": { + "Type": "AWS::SNS::Topic", + "Properties": { + "KmsMasterKeyId": { + "Ref": "ImportedEncryptionKeyBE10B2FC" + } + } + }, + "testsnssqsSnsTopicPolicy604079F2": { + "Type": "AWS::SNS::TopicPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe" + ], + "Condition": { + "StringEquals": { + "AWS:SourceOwner": { + "Ref": "AWS::AccountId" + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Sid": "TopicOwnerOnlyAccess" + }, + { + "Action": [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe" + ], + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "testsnssqsSnsTopic2CD0065B" + } + ] + } + }, + "testsnssqsqueueB02504BF": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "ImportedEncryptionKeyBE10B2FC", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "maxReceiveCount": 15 + } + } + }, + "testsnssqsqueuePolicyE64464B6": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + }, + "Sid": "HttpsOnly" + }, + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "testsnssqsSnsTopic2CD0065B" + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testsnssqsqueueB02504BF" + } + ] + } + }, + "testsnssqsqueuetestsnssqsSnsTopic752C989B046CB7AE": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "sqs", + "TopicArn": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Endpoint": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.existing-kms-key.ts b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.existing-kms-key.ts new file mode 100644 index 000000000..5ecdb5db0 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.existing-kms-key.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import { App, Stack } from "@aws-cdk/core"; +import { SnsToSqs, SnsToSqsProps } from "../lib"; +import { KeyProps } from '@aws-cdk/aws-kms'; +import * as kms from '@aws-cdk/aws-kms'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-sns-sqs'); +stack.templateOptions.description = 'Integration Test for aws-sns-sqs with existing KMS key'; + +// Definitions +const encryptionKeyProps: KeyProps = { + enableKeyRotation: true +}; +let key = new kms.Key(stack, 'ImportedEncryptionKey', encryptionKeyProps); +const props: SnsToSqsProps = { + enableEncryptionWithCustomerManagedKey: true, + encryptionKey: key +}; +new SnsToSqs(stack, 'test-sns-sqs', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..72400a6d2 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.no-arguments.expected.json @@ -0,0 +1,357 @@ +{ + "Description": "Integration Test for aws-sns-sqs", + "Resources": { + "testsnssqsdeadLetterQueue8DACC0A1": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": "alias/aws/sqs" + } + }, + "testsnssqsdeadLetterQueuePolicyAB8A9883": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testsnssqsdeadLetterQueue8DACC0A1" + } + ] + } + }, + "testsnssqsSnsTopic2CD0065B": { + "Type": "AWS::SNS::Topic", + "Properties": { + "KmsMasterKeyId": { + "Ref": "EncryptionKey1B843E66" + } + } + }, + "testsnssqsSnsTopicPolicy604079F2": { + "Type": "AWS::SNS::TopicPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe" + ], + "Condition": { + "StringEquals": { + "AWS:SourceOwner": { + "Ref": "AWS::AccountId" + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Sid": "TopicOwnerOnlyAccess" + }, + { + "Action": [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe" + ], + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "testsnssqsSnsTopic2CD0065B" + } + ] + } + }, + "testsnssqsqueueB02504BF": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "maxReceiveCount": 15 + } + } + }, + "testsnssqsqueuePolicyE64464B6": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + }, + "Sid": "HttpsOnly" + }, + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "testsnssqsSnsTopic2CD0065B" + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testsnssqsqueueB02504BF" + } + ] + } + }, + "testsnssqsqueuetestsnssqsSnsTopic752C989B046CB7AE": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "sqs", + "TopicArn": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Endpoint": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + } + }, + "EncryptionKey1B843E66": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "EnableKeyRotation": true + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.no-arguments.ts new file mode 100644 index 000000000..b34018dfa --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.no-arguments.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import { App, Stack } from "@aws-cdk/core"; +import { SnsToSqs, SnsToSqsProps } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-sns-sqs'); +stack.templateOptions.description = 'Integration Test for aws-sns-sqs'; + +// Definitions +const props: SnsToSqsProps = {}; + +new SnsToSqs(stack, 'test-sns-sqs', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.sns-managed-kms-key.expected.json b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.sns-managed-kms-key.expected.json new file mode 100644 index 000000000..3408036e3 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.sns-managed-kms-key.expected.json @@ -0,0 +1,355 @@ +{ + "Description": "Integration Test for aws-sns-sqs with SNS managed KMS key", + "Resources": { + "ImportedSQSEncryptionKey29533C9A": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "EnableKeyRotation": true + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testsnssqsdeadLetterQueue8DACC0A1": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": "alias/aws/sqs" + } + }, + "testsnssqsdeadLetterQueuePolicyAB8A9883": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testsnssqsdeadLetterQueue8DACC0A1" + } + ] + } + }, + "testsnssqsSnsTopic2CD0065B": { + "Type": "AWS::SNS::Topic", + "Properties": { + "KmsMasterKeyId": "alias/aws/sns" + } + }, + "testsnssqsSnsTopicPolicy604079F2": { + "Type": "AWS::SNS::TopicPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe" + ], + "Condition": { + "StringEquals": { + "AWS:SourceOwner": { + "Ref": "AWS::AccountId" + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Sid": "TopicOwnerOnlyAccess" + }, + { + "Action": [ + "SNS:Publish", + "SNS:RemovePermission", + "SNS:SetTopicAttributes", + "SNS:DeleteTopic", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:Receive", + "SNS:AddPermission", + "SNS:Subscribe" + ], + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "testsnssqsSnsTopic2CD0065B" + } + ] + } + }, + "testsnssqsqueueB02504BF": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "ImportedSQSEncryptionKey29533C9A", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "testsnssqsdeadLetterQueue8DACC0A1", + "Arn" + ] + }, + "maxReceiveCount": 15 + } + } + }, + "testsnssqsqueuePolicyE64464B6": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + }, + "Sid": "HttpsOnly" + }, + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "testsnssqsSnsTopic2CD0065B" + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testsnssqsqueueB02504BF" + } + ] + } + }, + "testsnssqsqueuetestsnssqsSnsTopic752C989B046CB7AE": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "sqs", + "TopicArn": { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + "Endpoint": { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.sns-managed-kms-key.ts b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.sns-managed-kms-key.ts new file mode 100644 index 000000000..32b2914f6 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/integ.sns-managed-kms-key.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import { App, Stack } from "@aws-cdk/core"; +import { SnsToSqs, SnsToSqsProps } from "../lib"; +import { KeyProps } from '@aws-cdk/aws-kms'; +import * as kms from '@aws-cdk/aws-kms'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-sns-sqs'); +stack.templateOptions.description = 'Integration Test for aws-sns-sqs with SNS managed KMS key'; + +// Definitions + +// Retrieve SNS managed key to encrypt the SNS Topic +const snsManagedKey = kms.Alias.fromAliasName(stack, 'sns-managed-key', 'alias/aws/sns'); + +// Create customer managed KMS CMK to encrypt the SQS Queue +const sqsEncryptionKeyProps: KeyProps = { + enableKeyRotation: true +}; +let sqsEncryptionKey = new kms.Key(stack, 'ImportedSQSEncryptionKey', sqsEncryptionKeyProps); + +// Create the SNS to SQS construct +const props: SnsToSqsProps = { + topicProps: { + masterKey: snsManagedKey + }, + queueProps: { + encryptionMasterKey: sqsEncryptionKey + }, + enableEncryptionWithCustomerManagedKey: false +}; + +new SnsToSqs(stack, 'test-sns-sqs', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/sns-sqs.test.ts b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/sns-sqs.test.ts new file mode 100644 index 000000000..18a67813a --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-sqs/test/sns-sqs.test.ts @@ -0,0 +1,218 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import { Stack } from "@aws-cdk/core"; +import { SnsToSqs, SnsToSqsProps } from "../lib"; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as sns from '@aws-cdk/aws-sns'; +import * as kms from '@aws-cdk/aws-kms'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Pattern deployment with new Topic, new Queue and +// default properties +// -------------------------------------------------------------- +test('Pattern deployment w/ new Topic, new Queue and default props', () => { + // Initial Setup + const stack = new Stack(); + const props: SnsToSqsProps = {}; + new SnsToSqs(stack, 'test-sns-sqs', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::SNS::Topic", { + KmsMasterKeyId: { + "Ref": "EncryptionKey1B843E66" + } + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::SQS::Queue", { + KmsMasterKeyId: { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + }); + // Assertion 4 + expect(stack).toHaveResource("AWS::SNS::Subscription", { + Protocol: "sqs", + TopicArn: { + "Ref": "testsnssqsSnsTopic2CD0065B" + }, + Endpoint: { + "Fn::GetAtt": [ + "testsnssqsqueueB02504BF", + "Arn" + ] + } + }); +}); + +// -------------------------------------------------------------- +// Pattern deployment with new Topic, new Queue, and +// overridden properties +// -------------------------------------------------------------- +test('Pattern deployment w/ new topic, new queue, and overridden props', () => { + // Initial Setup + const stack = new Stack(); + const props: SnsToSqsProps = { + topicProps: { + topicName: "new-topic", + }, + queueProps: { + queueName: "new-queue.fifo", + fifo: true + }, + enableEncryptionWithCustomerManagedKey: true, + encryptionKeyProps: { + enableKeyRotation: false + }, + deployDeadLetterQueue: false, + maxReceiveCount: 0 + }; + new SnsToSqs(stack, 'test-sns-sqs', props); + // Assertion 1 + expect(stack).toHaveResource("AWS::SNS::Topic", { + TopicName: "new-topic", + KmsMasterKeyId: { + "Ref": "EncryptionKey1B843E66" + } + }); + // Assertion 2 + expect(stack).toHaveResource("AWS::SQS::Queue", { + QueueName: "new-queue.fifo", + FifoQueue: true + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: false + }); +}); + +// -------------------------------------------------------------- +// Test the getter methods +// -------------------------------------------------------------- +test('Test getter methods', () => { + // Initial Setup + const stack = new Stack(); + const props: SnsToSqsProps = { + enableEncryptionWithCustomerManagedKey: true, + deployDeadLetterQueue: true, + maxReceiveCount: 0 + }; + const app = new SnsToSqs(stack, 'test-sns-sqs', props); + // Assertion 1 + expect(app.snsTopic !== null); + // Assertion 2 + expect(app.encryptionKey !== null); + // Assertion 3 + expect(app.sqsQueue !== null); + // Assertion 4 + expect(app.deadLetterQueue !== null); +}); + +// -------------------------------------------------------------- +// Test deployment with existing queue, and topic +// -------------------------------------------------------------- +test('Test deployment w/ existing queue, and topic', () => { + // Stack + const stack = new Stack(); + // Helper declaration + const topic = new sns.Topic(stack, "existing-topic-obj", { + topicName: 'existing-topic-obj' + }); + const queue = new sqs.Queue(stack, 'existing-queue-obj', { + queueName: 'existing-queue-obj' + }); + const app = new SnsToSqs(stack, 'sns-to-sqs-stack', { + existingTopicObj: topic, + existingQueueObj: queue + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(app.snsTopic !== null); + // Assertion 3 + expect(stack).toHaveResource("AWS::SNS::Topic", { + TopicName: "existing-topic-obj" + }); + // Assertion 4 + expect(stack).toHaveResource("AWS::SQS::Queue", { + QueueName: "existing-queue-obj" + }); +}); + +// -------------------------------------------------------------- +// Test deployment with imported encryption key +// -------------------------------------------------------------- +test('Test deployment with imported encryption key', () => { + // Stack + const stack = new Stack(); + // Setup + const kmsKey = new kms.Key(stack, 'imported-key', { + enableKeyRotation: false + }); + // Helper declaration + new SnsToSqs(stack, 'sns-to-sqs-stack', { + enableEncryptionWithCustomerManagedKey: true, + encryptionKey: kmsKey + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: false + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::SNS::Topic", { + KmsMasterKeyId: { + "Ref": "importedkey38675D68" + } + }); +}); + +// -------------------------------------------------------------- +// Test deployment with SNS managed KMS key +// -------------------------------------------------------------- +test('Test deployment with SNS managed KMS key', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new SnsToSqs(stack, 'sns-to-sqs-stack', { + topicProps: { + masterKey: kms.Alias.fromAliasName(stack, 'sns-managed-key', 'alias/aws/sns') + }, + queueProps: { + encryption: sqs.QueueEncryption.KMS + }, + enableEncryptionWithCustomerManagedKey: false + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::SNS::Topic", { + KmsMasterKeyId: "alias/aws/sns" + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::SQS::Queue", { + KmsMasterKeyId: { + "Fn::GetAtt": [ + "snstosqsstackqueueKey743636E7", + "Arn" + ] + } + }); +}); diff --git a/source/patterns/@aws-solutions-constructs/core/lib/sns-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/sns-helper.ts index 5e259528a..e18598b37 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/sns-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/sns-helper.ts @@ -118,7 +118,7 @@ export function buildTopic(scope: cdk.Construct, props?: BuildTopicProps): [sns. } // Set encryption properties // TODO: Look into using the AWS managed CMK by using 'alias/aws/sns' - if (!props.enableEncryption || props.enableEncryption === true) { + if (props.enableEncryption === undefined || props.enableEncryption === true) { if (props.encryptionKey) { snsTopicProps.masterKey = props.encryptionKey; } else { @@ -132,4 +132,4 @@ export function buildTopic(scope: cdk.Construct, props?: BuildTopicProps): [sns. applySecureTopicPolicy(topic); return [topic, snsTopicProps.masterKey]; -} \ No newline at end of file +} diff --git a/source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts index c42052e8a..f758aacb7 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts @@ -15,9 +15,11 @@ import * as sqs from '@aws-cdk/aws-sqs'; import * as defaults from './sqs-defaults'; import * as cdk from '@aws-cdk/core'; +import * as kms from '@aws-cdk/aws-kms'; import { overrideProps } from './utils'; import { AccountPrincipal, Effect, PolicyStatement, AnyPrincipal } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; +import {buildEncryptionKey} from "./kms-helper"; export interface BuildQueueProps { /** @@ -38,6 +40,19 @@ export interface BuildQueueProps { * @default - Default props are used. */ readonly deadLetterQueue?: sqs.DeadLetterQueue + /** + * Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in + * the encryptionKey property for this construct. + * + * @default - false (encryption enabled with a KMS key managed by SQS). + */ + readonly enableEncryption?: boolean + /** + * An optional, imported encryption key to encrypt the SQS queue with. + * + * @default - not specified. + */ + readonly encryptionKey?: kms.Key } export function buildQueue(scope: cdk.Construct, id: string, props?: BuildQueueProps): sqs.Queue { @@ -47,17 +62,19 @@ export function buildQueue(scope: cdk.Construct, id: string, props?: BuildQueueP // If an existingQueueObj is not specified if (!props.existingQueueObj) { // Deploy the queue - return deployQueue(scope, id, props.queueProps, props.deadLetterQueue); + return deployQueue(scope, id, props.queueProps, props.deadLetterQueue, props.enableEncryption, props.encryptionKey); // If an existingQueueObj is specified, return that object as the queue to be used } else { - return props.existingQueueObj; + return props.existingQueueObj } } function deployQueue(scope: cdk.Construct, id: string, queuePropsParam?: sqs.QueueProps, - deadLetterQueueParam?: sqs.DeadLetterQueue): sqs.Queue { + deadLetterQueueParam?: sqs.DeadLetterQueue, + enableEncryptionParam?: boolean, + encryptionKeyParam?: kms.Key): sqs.Queue { // Setup the queue let queueProps; @@ -72,7 +89,14 @@ function deployQueue(scope: cdk.Construct, if (deadLetterQueueParam) { queueProps.deadLetterQueue = deadLetterQueueParam; } - + // Set encryption properties + if (enableEncryptionParam === true) { + if (encryptionKeyParam) { + queueProps.encryptionMasterKey = encryptionKeyParam; + } else { + queueProps.encryptionMasterKey = buildEncryptionKey(scope); + } + } const queue = new sqs.Queue(scope, id, queueProps); applySecureQueuePolicy(queue); @@ -150,4 +174,4 @@ function applySecureQueuePolicy(queue: sqs.Queue): void { } }) ); -} \ No newline at end of file +} diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap index c6a989cd2..ad6c9de4c 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap @@ -588,6 +588,286 @@ Object { } `; +exports[`Test deployment w/ imported encryption key 1`] = ` +Object { + "Resources": Object { + "EncryptionKey1B843E66": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "EnableKeyRotation": true, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "existingqueue03D57A53": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "QueueName": "existing-queue", + }, + "Type": "AWS::SQS::Queue", + }, + "existingqueuePolicy8BCB024D": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "existingqueue03D57A53", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "existingqueue03D57A53", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "existingqueue03D57A53", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + }, +} +`; + +exports[`Test deployment without imported encryption key 1`] = ` +Object { + "Resources": Object { + "EncryptionKey1B843E66": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "EnableKeyRotation": true, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "existingqueue03D57A53": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "QueueName": "existing-queue", + }, + "Type": "AWS::SQS::Queue", + }, + "existingqueuePolicy8BCB024D": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:RemovePermission", + "sqs:AddPermission", + "sqs:SetQueueAttributes", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "existingqueue03D57A53", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "existingqueue03D57A53", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "existingqueue03D57A53", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + }, +} +`; + exports[`Test existingQueueObj 1`] = ` Object { "Resources": Object { diff --git a/source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts index dd343e7e1..b7fc41dda 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts @@ -119,4 +119,55 @@ test('Test existingQueueObj', () => { }); // Assertion 1 expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); -}); \ No newline at end of file +}); + +// -------------------------------------------------------------- +// Test deployment w/ imported encryption key +// -------------------------------------------------------------- +test('Test deployment w/ imported encryption key', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildQueue(stack, 'existing-queue', { + queueProps: { + queueName: 'existing-queue' + }, + enableEncryption: true, + encryptionKey: defaults.buildEncryptionKey(stack) + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::SQS::Queue", { + QueueName: "existing-queue" + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: true + }); +}); + +// -------------------------------------------------------------- +// Test deployment without imported encryption key +// -------------------------------------------------------------- +test('Test deployment without imported encryption key', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildQueue(stack, 'existing-queue', { + queueProps: { + queueName: 'existing-queue' + }, + enableEncryption: true + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::SQS::Queue", { + QueueName: "existing-queue" + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: true + }); +});