diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.eslintignore new file mode 100644 index 000000000..e6f7801ea --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.eslintignore @@ -0,0 +1,4 @@ +lib/*.js +test/*.js +*.d.ts +coverage diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.gitignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.gitignore new file mode 100644 index 000000000..963955620 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.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 diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.npmignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.npmignore new file mode 100644 index 000000000..1ce9af351 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/.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 diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/README.md b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/README.md new file mode 100644 index 000000000..e47c799ba --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/README.md @@ -0,0 +1,88 @@ +# aws-events-rule-sns 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_events_rule_sns`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-events-rule-sns`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.eventsrulesns`| + +This AWS Solutions Construct implements an AWS Events rule and an AWS SNS Topic. + +Here is a minimal deployable pattern definition: + +``` typescript +import { EventsRuleToSNSTopicProps, EventsRuleToSNSTopic } from "@aws-solutions-constructs/aws-events-rule-sns"; + +const props: EventsRuleToSNSTopicProps = { + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)), + }, + topicsProps: { + displayName: 'event-rule-sns' + } +}; + +new EventsRuleToSNSTopic(this, 'test-events-rule-sns', props); +``` + +## Initializer + +``` text +new EventsRuleToSNSTopic(scope: Construct, id: string, props: EventsRuleToSNSTopicProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`EventsRuleToSNSTopicProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|eventRuleProps|[`events.RuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.RuleProps.html)|User provided eventRuleProps to override the defaults. | +|existingTopicObj?|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Existing instance of SNS Topic object, if this is set then the topicProps is ignored.| +|topicProps?|[`sns.TopicProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.TopicProps.html)|User provided props to override the default props for the SNS Topic. | + + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|eventsRule|[`events.Rule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.Rule.html)|Returns an instance of events.Rule created by the construct| +|snsTopic|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|Returns an instance of sns.Topic created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudWatch Events Rule +* Grant least privilege permissions to CloudWatch Events to publish to the SNS Topic + +### Amazon SNS Topic +* Configure least privilege access permissions for SNS Topic +* Enable server-side encryption forSNS Topic using AWS 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. \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/architecture.png b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/architecture.png new file mode 100644 index 000000000..cc9194c3a Binary files /dev/null and b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/architecture.png differ diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/lib/index.ts new file mode 100644 index 000000000..430a5cbf0 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/lib/index.ts @@ -0,0 +1,83 @@ +/** + * 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. + */ + +import * as sns from '@aws-cdk/aws-sns'; +import * as events from '@aws-cdk/aws-events'; +import * as defaults from '@aws-solutions-constructs/core'; +import { Construct } from '@aws-cdk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; +import { ArnPrincipal } from '@aws-cdk/aws-iam'; + +export interface EventsRuleToSNSTopicProps { + /** + * User provided props to override the default props for the SNS Topic. + * + * @default - Default props are used + */ + readonly topicsProps?: sns.TopicProps + /** + * User provided eventRuleProps to override the defaults + * + * @default - None + */ + readonly eventRuleProps: events.RuleProps + /** + * Existing instance of SNS Topic object, if this is set then topicProps is ignored. + * + * @default - Default props are used + */ + readonly existingTopicObj?: sns.Topic, +} + +export class EventsRuleToSNSTopic extends Construct { + public readonly snsTopic: sns.Topic; + public readonly eventsRule: events.Rule; + + + /** + * @summary Constructs a new instance of the EventRuleToSns class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {EventsRuleToSNSTopicProps} props - user provided props for the construct. + * @since 1.61.1 + * @access public + */ + constructor(scope: Construct, id: string, props: EventsRuleToSNSTopicProps) { + super(scope, id); + + //Setup the sns topic. + [this.snsTopic] = defaults.buildTopic(this, { + existingTopicObj: props.existingTopicObj, + topicProps: props.topicsProps + }); + + //Setup the event rule target as sns topic. + const topicEventTarget: events.IRuleTarget = { + bind: () => ({ + id: this.snsTopic.topicName, + arn: this.snsTopic.topicArn + }) + } + + //Setup up the event rule property. + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([topicEventTarget]); + const eventsRuleProps = overrideProps(defaultEventsRuleProps, props.eventRuleProps, true); + + //Setup up the event rule. + this.eventsRule = new events.Rule(this, 'EventsRule', eventsRuleProps); + + //Setup up the grant policy for event to be able to publish to the sns topic. + this.snsTopic.grantPublish(new ArnPrincipal(this.eventsRule.ruleArn)) + } + +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/package.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/package.json new file mode 100644 index 000000000..613043b96 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/package.json @@ -0,0 +1,82 @@ +{ + "name": "@aws-solutions-constructs/aws-events-rule-sns", + "version": "1.61.1", + "description": "CDK Constructs for deploying AWS Events Rule that invokes AWS SNS", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "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-no-clean": "cdk-integ --no-clean", + "integ-assert": "cdk-integ-assert", + "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" + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-events-rule-sns" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.eventsrulesns", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "eventsrulesns" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.EventsRuleSns", + "packageId": "Amazon.Constructs.AWS.EventsRuleSns", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-events-rule-sns", + "module": "aws_solutions_constructs.aws_events_rule_sns" + } + } + }, + "dependencies": { + "@aws-cdk/aws-events": "~1.61.1", + "@aws-cdk/aws-iam": "~1.61.1", + "@aws-cdk/aws-sns": "~1.61.1", + "@aws-cdk/core": "~1.61.1", + "@aws-solutions-constructs/core": "~1.61.1", + "constructs": "^3.0.4" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.61.1", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-sns": "~1.61.1", + "@aws-cdk/core": "~1.61.1", + "@aws-cdk/aws-events": "~1.61.1", + "@aws-cdk/aws-iam": "~1.61.1", + "@aws-cdk/aws-kms": "~1.61.1", + "@aws-solutions-constructs/core": "~1.61.1", + "constructs": "^3.0.4" + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/__snapshots__/events-rule-sns-topic.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/__snapshots__/events-rule-sns-topic.test.js.snap new file mode 100644 index 000000000..31147fb2b --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/__snapshots__/events-rule-sns-topic.test.js.snap @@ -0,0 +1,132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test EventsRuleToSNS default params 1`] = ` +Object { + "Resources": Object { + "testeventsrulesnsEventsRule5F1C01CC": Object { + "Properties": Object { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD", + }, + "Id": Object { + "Fn::GetAtt": Array [ + "testeventsrulesnsSnsTopicCEB51DAD", + "TopicName", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "testeventsrulesnsSnsTopicCEB51DAD": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sns", + }, + "Type": "AWS::SNS::Topic", + }, + "testeventsrulesnsSnsTopicPolicyF1A0FC73": 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": "testeventsrulesnsSnsTopicCEB51DAD", + }, + "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": "testeventsrulesnsSnsTopicCEB51DAD", + }, + "Sid": "HttpsOnly", + }, + Object { + "Action": "sns:Publish", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "testeventsrulesnsEventsRule5F1C01CC", + "Arn", + ], + }, + }, + "Resource": Object { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD", + }, + "Sid": "2", + }, + ], + "Version": "2012-10-17", + }, + "Topics": Array [ + Object { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD", + }, + ], + }, + "Type": "AWS::SNS::TopicPolicy", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/events-rule-sns-topic.test.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/events-rule-sns-topic.test.ts new file mode 100644 index 000000000..fbfad49ed --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/events-rule-sns-topic.test.ts @@ -0,0 +1,178 @@ +/** + * 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. + */ + +import { SynthUtils } from '@aws-cdk/assert'; +import * as cdk from "@aws-cdk/core"; +import * as events from "@aws-cdk/aws-events"; +import '@aws-cdk/assert/jest'; +import { EventsRuleToSNSTopic, EventsRuleToSNSTopicProps } from "../lib" + + +function deployNewFunc(stack: cdk.Stack) { + const props: EventsRuleToSNSTopicProps = { + eventRuleProps: { + schedule: events.Schedule.rate(cdk.Duration.minutes(5)) + } + } + return new EventsRuleToSNSTopic(stack, 'test-events-rule-sns', props); +} + +function getStack() { + const app = new cdk.App() + return new cdk.Stack(app, 'stack') +} + +test('snapshot test EventsRuleToSNS default params', () => { + const stack = getStack() + deployNewFunc(stack) + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check the sns topic resource is created', () => { + const stack = getStack() + deployNewFunc(stack) + expect(stack).toHaveResource('AWS::SNS::Topic', {}) +}) + +test('check if the event rule has permission/policy in place in sns for it to be able to publish to the topic', () => { + const stack = getStack() + deployNewFunc(stack) + expect(stack).toHaveResource('AWS::SNS::TopicPolicy', { + 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": "testeventsrulesnsSnsTopicCEB51DAD", + }, + 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": "testeventsrulesnsSnsTopicCEB51DAD", + }, + Sid: "HttpsOnly", + }, + { + Action: "sns:Publish", + Effect: "Allow", + Principal: { + "AWS": { + "Fn::GetAtt": [ + "testeventsrulesnsEventsRule5F1C01CC", + "Arn", + ], + }, + }, + Resource: { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD", + }, + Sid: "2", + }, + ], + Version: "2012-10-17", + }, + Topics: [ + { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD", + }, + ], + }, + ) +}) + +test('check events rule properties for deploy: true', () => { + const stack = getStack() + deployNewFunc(stack) + + expect(stack).toHaveResource('AWS::Events::Rule', { + ScheduleExpression: "rate(5 minutes)", + State: "ENABLED", + Targets: [ + { + Arn: { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD" + }, + Id: { + "Fn::GetAtt": [ + "testeventsrulesnsSnsTopicCEB51DAD", + "TopicName" + ] + } + } + ] + }) +}) + +test('check properties', () => { + const stack = new cdk.Stack(); + + const construct: EventsRuleToSNSTopic = deployNewFunc(stack); + + expect(construct.eventsRule !== null); + expect(construct.snsTopic !== null); +}); diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/integ.events-rule-no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/integ.events-rule-no-arguments.expected.json new file mode 100644 index 000000000..0b0358fe6 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/integ.events-rule-no-arguments.expected.json @@ -0,0 +1,128 @@ +{ + "Resources": { + "testeventsrulesnsEventsRule5F1C01CC": { + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD" + }, + "Id": { + "Fn::GetAtt": [ + "testeventsrulesnsSnsTopicCEB51DAD", + "TopicName" + ] + } + } + ] + }, + "Type": "AWS::Events::Rule" + }, + "testeventsrulesnsSnsTopicCEB51DAD": { + "Properties": { + "KmsMasterKeyId": "alias/aws/sns" + }, + "Type": "AWS::SNS::Topic" + }, + "testeventsrulesnsSnsTopicPolicyF1A0FC73": { + "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": "testeventsrulesnsSnsTopicCEB51DAD" + }, + "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": "testeventsrulesnsSnsTopicCEB51DAD" + }, + "Sid": "HttpsOnly" + }, + { + "Action": "sns:Publish", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "testeventsrulesnsEventsRule5F1C01CC", + "Arn" + ] + } + }, + "Resource": { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD" + }, + "Sid": "2" + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "testeventsrulesnsSnsTopicCEB51DAD" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/integ.events-rule-no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/integ.events-rule-no-arguments.ts new file mode 100644 index 000000000..aa53f6dad --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sns/test/integ.events-rule-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. + */ + +import { Duration } from '@aws-cdk/core' +import { EventsRuleToSNSTopic, EventsRuleToSNSTopicProps } from '../lib' +import * as events from '@aws-cdk/aws-events' +import { App, Stack } from '@aws-cdk/core' + +const app = new App(); +const stack = new Stack(app, 'test-events-rule-sns-stack'); + +const props: EventsRuleToSNSTopicProps = { + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +} + +new EventsRuleToSNSTopic(stack, 'test-events-rule-sns', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.eslintignore new file mode 100644 index 000000000..e6f7801ea --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.eslintignore @@ -0,0 +1,4 @@ +lib/*.js +test/*.js +*.d.ts +coverage diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.gitignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.gitignore new file mode 100644 index 000000000..963955620 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-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 diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.npmignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/.npmignore new file mode 100644 index 000000000..1ce9af351 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-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 diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/README.md new file mode 100644 index 000000000..2d067c2f3 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/README.md @@ -0,0 +1,94 @@ +# aws-events-rule-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_events_rule_sqs`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-events-rule-sqs`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.eventsrulesqs`| + +This AWS Solutions Construct implements an AWS Events rule and an AWS SQS Queue. + +Here is a minimal deployable pattern definition: + +``` typescript +import { EventsRuleToSQSQueueProps, EventsRuleToSQSQueue } from ('@aws-solutions-constructs/aws-events-rule-sqs'); + +const props: EventsRuleToSQSQueueProps = { + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + }, + queueProps: { + queueName: 'event-rule-sqs', + fifo: true + }, + enableQueuePurging: false, + deployDeadLetterQueue: false +}; + +new EventsRuleToSQSQueue(stack, 'test-events-rule-sqs', props); +``` + +## Initializer + +``` text +new EventsRuleToSQSQueue(scope: Construct, id: string, props: EventsRuleToSQSQueueProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`EventsRuleToSQSQueueProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|eventRuleProps|[`events.RuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.RuleProps.html)|User provided eventRuleProps to override the defaults. | +|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)|User provided props to override the default props for the SQS Queue. | +|enableQueuePurging?|`boolean`|Whether to grant additional permissions to the Lambda function enabling it to purge the SQS queue. Defaults to `false`.| +|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 queue. Only used if the `deployDeadLetterQueue` property is set to true.| +|maxReceiveCount?|`number`|The number of times a message can be unsuccessfully dequeued before being moved to the dead letter queue. Defaults to `15`.| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|eventsRule|[`events.Rule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.Rule.html)|Returns an instance of events.Rule created by the construct| +|sqsQueue|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of sqs.Queue created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudWatch Events Rule +* Grant least privilege permissions to CloudWatch Events to publish to the SQS Queue + +### Amazon SQS Queue +* Deploy SQS dead-letter queue for the source SQS Queue. +* Enable server-side encryption for source SQS Queue using AWS 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. \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/architecture.png b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/architecture.png new file mode 100644 index 000000000..7c6a66746 Binary files /dev/null and b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/architecture.png differ diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/lib/index.ts new file mode 100644 index 000000000..41bc3f637 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/lib/index.ts @@ -0,0 +1,123 @@ +/** + * 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. + */ + +import * as sqs from '@aws-cdk/aws-sqs'; +import * as events from '@aws-cdk/aws-events'; +import * as defaults from '@aws-solutions-constructs/core'; +import { ArnPrincipal } from '@aws-cdk/aws-iam'; +import { Construct } from '@aws-cdk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; + +/** + * @summary The properties for the EventsRuleToSQS Construct + */ +export interface EventsRuleToSQSProps { + /** + * User provided eventRuleProps to override the defaults + * + * @default - None + */ + readonly eventRuleProps: events.RuleProps + /** + * Existing instance of SQS queue object, if this is set then the queueProps is ignored. + * + * @default - None + */ + readonly existingQueueObj?: sqs.Queue, + /** + * User provided props to override the default props for the SQS queue. + * + * @default - Default props are used + */ + readonly queueProps?: sqs.QueueProps, + /** + * Whether to grant additional permissions to the Lambda function enabling it to purge the SQS queue. + * + * @default - "false", disabled by default. + */ + readonly enableQueuePurging?: boolean, + /** + * 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 +} + +export class EventsRuleToSQS extends Construct { + public readonly sqsQueue: sqs.Queue; + public readonly deadLetterQueue: sqs.DeadLetterQueue | undefined; + public readonly eventsRule: events.Rule; + + /** + * @summary Constructs a new instance of the EventsRuleToSQS class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {EventsRuleToSQSProps} props - user provided props for the construct + * @since 1.61.1 + * @access public + */ + constructor(scope: Construct, id: string, props: EventsRuleToSQSProps) { + super(scope, id); + + // Setup the dead letter queue, if applicable + if (!props.existingQueueObj && (props.deployDeadLetterQueue || props.deployDeadLetterQueue === undefined)) { + const [dlq] = defaults.buildQueue(this, 'deadLetterQueue', { + queueProps: props.deadLetterQueueProps + }); + this.deadLetterQueue = defaults.buildDeadLetterQueue({ + deadLetterQueue: dlq, + maxReceiveCount: props.maxReceiveCount + }); + } + + // Setup the queue + [this.sqsQueue] = defaults.buildQueue(this, 'queue', { + existingQueueObj: props.existingQueueObj, + queueProps: props.queueProps, + deadLetterQueue: this.deadLetterQueue + }); + + const sqsEventTarget: events.IRuleTarget = { + bind: () => ({ + id: this.sqsQueue.queueName, + arn: this.sqsQueue.queueArn + }) + }; + + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([sqsEventTarget]); + const eventsRuleProps = overrideProps(defaultEventsRuleProps, props.eventRuleProps, true); + + this.eventsRule = new events.Rule(this, 'EventsRule', eventsRuleProps); + + // Enable queue purging permissions for the event rule, if enabled + if (props.enableQueuePurging) { + this.sqsQueue.grantPurge(new ArnPrincipal(this.eventsRule.ruleArn)); + } + + //Policy for event to be able to send messages to the queue + this.sqsQueue.grantSendMessages(new ArnPrincipal(this.eventsRule.ruleArn)) + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/package.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/package.json new file mode 100644 index 000000000..dace224b7 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/package.json @@ -0,0 +1,82 @@ +{ + "name": "@aws-solutions-constructs/aws-events-rule-sqs", + "version": "1.61.1", + "description": "CDK Constructs for deploying AWS Events Rule that invokes AWS SQS", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "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-no-clean": "cdk-integ --no-clean", + "integ-assert": "cdk-integ-assert", + "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" + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-events-rule-sqs" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.eventsrulesqs", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "eventsrulesqs" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.EventsRuleSQS", + "packageId": "Amazon.Constructs.AWS.EventsRuleSQS", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-events-rule-sqs", + "module": "aws_solutions_constructs.aws_events_rule_sqs" + } + } + }, + "dependencies": { + "@aws-cdk/aws-events": "~1.61.1", + "@aws-cdk/aws-iam": "~1.61.1", + "@aws-cdk/aws-sqs": "~1.61.1", + "@aws-cdk/core": "~1.61.1", + "@aws-solutions-constructs/core": "~1.61.1", + "constructs": "^3.0.4" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.61.1", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-sqs": "~1.61.1", + "@aws-cdk/core": "~1.61.1", + "@aws-cdk/aws-events": "~1.61.1", + "@aws-cdk/aws-iam": "~1.61.1", + "@aws-cdk/aws-kms": "~1.61.1", + "@aws-solutions-constructs/core": "~1.61.1", + "constructs": "^3.0.4" + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/__snapshots__/events-rule-sqs-queue.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/__snapshots__/events-rule-sqs-queue.test.js.snap new file mode 100644 index 000000000..aad5e56dc --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/__snapshots__/events-rule-sqs-queue.test.js.snap @@ -0,0 +1,212 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test EventsRuleToSQS default params 1`] = ` +Object { + "Resources": Object { + "testeventsrulesqsEventsRule06054F3F": Object { + "Properties": Object { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + "Id": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsqueueAACD0364", + "QueueName", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "testeventsrulesqsdeadLetterQueueA4A15A1C": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, + "testeventsrulesqsdeadLetterQueuePolicyB769DC48": 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 [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "testeventsrulesqsdeadLetterQueueA4A15A1C", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "testeventsrulesqsqueueAACD0364": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn", + ], + }, + "maxReceiveCount": 15, + }, + }, + "Type": "AWS::SQS::Queue", + }, + "testeventsrulesqsqueuePolicy6D46CF4A": 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 [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + "Sid": "QueueOwnerOnlyAccess", + }, + Object { + "Action": "SQS:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + "Sid": "HttpsOnly", + }, + Object { + "Action": Array [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsEventsRule06054F3F", + "Arn", + ], + }, + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Queues": Array [ + Object { + "Ref": "testeventsrulesqsqueueAACD0364", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/events-rule-sqs-queue.test.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/events-rule-sqs-queue.test.ts new file mode 100644 index 000000000..f4b91b08c --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/events-rule-sqs-queue.test.ts @@ -0,0 +1,212 @@ +/** + * 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. + */ + +import * as cdk from '@aws-cdk/core'; +import { EventsRuleToSQS, EventsRuleToSQSProps } from '../lib' +import * as events from "@aws-cdk/aws-events"; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + + +function deployNewFunc(stack: cdk.Stack) { + const props: EventsRuleToSQSProps = { + eventRuleProps: { + schedule: events.Schedule.rate(cdk.Duration.minutes(5)) + } + } + return new EventsRuleToSQS(stack, 'test-events-rule-sqs', props); +} + +function getStack() { + const app = new cdk.App() + return new cdk.Stack(app, 'stack') +} + +test('snapshot test EventsRuleToSQS default params', () => { + const stack = getStack() + deployNewFunc(stack) + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check the sqs queue properties', () => { + const stack = getStack() + deployNewFunc(stack) + expect(stack).toHaveResource('AWS::SQS::Queue', {}) +}) + +test('check if the event rule has permission/policy in place in sqs queue for it to be able to send messages to the queue.', () => { + const stack = getStack() + deployNewFunc(stack) + expect(stack).toHaveResource('AWS::SQS::QueuePolicy', { + 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": [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + Sid: "QueueOwnerOnlyAccess", + }, + { + Action: "SQS:*", + Condition: { + "Bool": { + "aws:SecureTransport": "false", + }, + }, + Effect: "Deny", + Principal: "*", + Resource: { + "Fn::GetAtt": [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + Sid: "HttpsOnly", + }, + { + Action: [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + ], + Effect: "Allow", + Principal: { + "AWS": { + "Fn::GetAtt": [ + "testeventsrulesqsEventsRule06054F3F", + "Arn", + ], + }, + }, + Resource: { + "Fn::GetAtt": [ + "testeventsrulesqsqueueAACD0364", + "Arn", + ], + }, + }, + ], + Version: "2012-10-17" + }, + Queues: [ + { + "Ref": "testeventsrulesqsqueueAACD0364", + } + ] + }) +}) + +test('check if the dead letter queue policy is setup', () => { + const stack = getStack() + deployNewFunc(stack) + expect(stack).toHaveResource('AWS::SQS::QueuePolicy', { + 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": [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn", + ], + }, + Sid: "QueueOwnerOnlyAccess", + }, + { + Action: "SQS:*", + Condition: { + "Bool": { + "aws:SecureTransport": "false", + }, + }, + Effect: "Deny", + Principal: "*", + Resource: { + "Fn::GetAtt": [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn", + ], + }, + Sid: "HttpsOnly", + }, + ], + Version: "2012-10-17", + }, + Queues: [ + { + "Ref": "testeventsrulesqsdeadLetterQueueA4A15A1C", + }, + ] + }) + +}) + + \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-existing-queue.expected.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-existing-queue.expected.json new file mode 100644 index 000000000..ef8f80b68 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-existing-queue.expected.json @@ -0,0 +1,124 @@ +{ + "Resources": { + "testeventsrulesqsEventsRule06054F3F": { + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "existingqueue03D57A53", + "Arn" + ] + }, + "Id": { + "Fn::GetAtt": [ + "existingqueue03D57A53", + "QueueName" + ] + } + } + ] + }, + "Type": "AWS::Events::Rule" + }, + "existingqueue03D57A53": { + "Properties": { + "KmsMasterKeyId": "alias/aws/sqs" + }, + "Type": "AWS::SQS::Queue" + }, + "existingqueuePolicy8BCB024D": { + "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": [ + "existingqueue03D57A53", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "existingqueue03D57A53", + "Arn" + ] + }, + "Sid": "HttpsOnly" + }, + { + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "testeventsrulesqsEventsRule06054F3F", + "Arn" + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "existingqueue03D57A53", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "existingqueue03D57A53" + } + ] + }, + "Type": "AWS::SQS::QueuePolicy" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-existing-queue.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-existing-queue.ts new file mode 100644 index 000000000..5e65fae4c --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-existing-queue.ts @@ -0,0 +1,33 @@ +/** + * 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. + */ + +import { Duration } from '@aws-cdk/core' +import { EventsRuleToSQSProps, EventsRuleToSQS } from '../lib' +import * as events from '@aws-cdk/aws-events' +import { App, Stack } from '@aws-cdk/core' +import * as Defaults from '@aws-solutions-constructs/core'; + +const app = new App(); +const stack = new Stack(app, 'test-events-rule-sqs-stack'); + +const [existingQueueObj] = Defaults.buildQueue(stack, 'existing-queue', {}) + +const props: EventsRuleToSQSProps = { + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + }, + existingQueueObj: existingQueueObj +} + +new EventsRuleToSQS(stack, 'test-events-rule-sqs', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-no-arguments.expected.json new file mode 100644 index 000000000..c5af258a5 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-no-arguments.expected.json @@ -0,0 +1,208 @@ +{ + "Resources": { + "testeventsrulesqsEventsRule06054F3F": { + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "testeventsrulesqsqueueAACD0364", + "Arn" + ] + }, + "Id": { + "Fn::GetAtt": [ + "testeventsrulesqsqueueAACD0364", + "QueueName" + ] + } + } + ] + }, + "Type": "AWS::Events::Rule" + }, + "testeventsrulesqsdeadLetterQueueA4A15A1C": { + "Properties": { + "KmsMasterKeyId": "alias/aws/sqs" + }, + "Type": "AWS::SQS::Queue" + }, + "testeventsrulesqsdeadLetterQueuePolicyB769DC48": { + "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": [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn" + ] + }, + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testeventsrulesqsdeadLetterQueueA4A15A1C" + } + ] + }, + "Type": "AWS::SQS::QueuePolicy" + }, + "testeventsrulesqsqueueAACD0364": { + "Properties": { + "KmsMasterKeyId": "alias/aws/sqs", + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "testeventsrulesqsdeadLetterQueueA4A15A1C", + "Arn" + ] + }, + "maxReceiveCount": 15 + } + }, + "Type": "AWS::SQS::Queue" + }, + "testeventsrulesqsqueuePolicy6D46CF4A": { + "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": [ + "testeventsrulesqsqueueAACD0364", + "Arn" + ] + }, + "Sid": "QueueOwnerOnlyAccess" + }, + { + "Action": "SQS:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": { + "Fn::GetAtt": [ + "testeventsrulesqsqueueAACD0364", + "Arn" + ] + }, + "Sid": "HttpsOnly" + }, + { + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "testeventsrulesqsEventsRule06054F3F", + "Arn" + ] + } + }, + "Resource": { + "Fn::GetAtt": [ + "testeventsrulesqsqueueAACD0364", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "testeventsrulesqsqueueAACD0364" + } + ] + }, + "Type": "AWS::SQS::QueuePolicy" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-no-arguments.ts new file mode 100644 index 000000000..16d0009e4 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-sqs/test/integ.events-rule-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. + */ + +import { Duration } from '@aws-cdk/core' +import { EventsRuleToSQSProps, EventsRuleToSQS } from '../lib' +import * as events from '@aws-cdk/aws-events' +import { App, Stack } from '@aws-cdk/core' + +const app = new App(); +const stack = new Stack(app, 'test-events-rule-sqs-stack'); + +const props: EventsRuleToSQSProps = { + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +} + +new EventsRuleToSQS(stack, 'test-events-rule-sqs', props); +app.synth(); \ No newline at end of file