diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.assets.json index 815adb808f6c1..134a9dade2e45 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.assets.json @@ -1,7 +1,7 @@ { - "version": "35.0.0", + "version": "36.0.0", "files": { - "c917d5a2b2e16b9e3cd0679e1dee0539129223bcca595b66788045cc8fc23e20": { + "cace790a33cdbfeda534014c535d183a1c3e0533c1c5f5ea93b0b2feb9901fea": { "source": { "path": "SNSInteg.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "c917d5a2b2e16b9e3cd0679e1dee0539129223bcca595b66788045cc8fc23e20.json", + "objectKey": "cace790a33cdbfeda534014c535d183a1c3e0533c1c5f5ea93b0b2feb9901fea.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.template.json index 69954cad9b6b7..a74d003d39a88 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/SNSInteg.template.json @@ -110,6 +110,14 @@ } ] } + }, + "MyTopicSignatureVersionEDDB6A3B": { + "Type": "AWS::SNS::Topic", + "Properties": { + "DisplayName": "fooDisplayNameSignatureVersion", + "SignatureVersion": "2", + "TopicName": "fooTopicSignatureVersion" + } } }, "Parameters": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/cdk.out index c5cb2e5de6344..1f0068d32659a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"35.0.0"} \ No newline at end of file +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/integ.json index d9fab2006a00e..72d9cc0b958b9 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "35.0.0", + "version": "36.0.0", "testCases": { "integ.sns": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/manifest.json index 08016c12ab007..a3dc7a8aed489 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "35.0.0", + "version": "36.0.0", "artifacts": { "SNSInteg.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/c917d5a2b2e16b9e3cd0679e1dee0539129223bcca595b66788045cc8fc23e20.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/cace790a33cdbfeda534014c535d183a1c3e0533c1c5f5ea93b0b2feb9901fea.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -58,6 +58,12 @@ "data": "Policy23B91518" } ], + "/SNSInteg/MyTopicSignatureVersion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyTopicSignatureVersionEDDB6A3B" + } + ], "/SNSInteg/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/tree.json index c7a2a6387aacc..c628ee349b99e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.js.snapshot/tree.json @@ -205,6 +205,32 @@ "version": "10.3.0" } }, + "MyTopicSignatureVersion": { + "id": "MyTopicSignatureVersion", + "path": "SNSInteg/MyTopicSignatureVersion", + "children": { + "Resource": { + "id": "Resource", + "path": "SNSInteg/MyTopicSignatureVersion/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SNS::Topic", + "aws:cdk:cloudformation:props": { + "displayName": "fooDisplayNameSignatureVersion", + "signatureVersion": "2", + "topicName": "fooTopicSignatureVersion" + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "SNSInteg/BootstrapVersion", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.ts index 5e1adcf34bc2e..d482aea329bec 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-sns/test/integ.sns.ts @@ -40,6 +40,12 @@ class SNSInteg extends Stack { successFeedbackRole: feedbackRole, successFeedbackSampleRate: 50, }); + + new Topic(this, 'MyTopicSignatureVersion', { + topicName: 'fooTopicSignatureVersion', + displayName: 'fooDisplayNameSignatureVersion', + signatureVersion: '2', + }); } } diff --git a/packages/aws-cdk-lib/aws-sns/README.md b/packages/aws-cdk-lib/aws-sns/README.md index f8b625b4eb3e9..de05ba11e95ae 100644 --- a/packages/aws-cdk-lib/aws-sns/README.md +++ b/packages/aws-cdk-lib/aws-sns/README.md @@ -19,6 +19,19 @@ const topic = new sns.Topic(this, 'Topic', { }); ``` +Add an SNS Topic to your stack with a specified signature version, which corresponds +to the hashing algorithm used while creating the signature of the notifications, +subscription confirmations, or unsubscribe confirmation messages sent by Amazon SNS. + +The default signature version is `1` (`SHA1`). +SNS also supports signature version `2` (`SHA256`). + +```ts +const topic = new sns.Topic(this, 'Topic', { + signatureVersion: '2', +}); +``` + Note that FIFO topics require a topic name to be provided. The required `.fifo` suffix will be automatically generated and added to the topic name if it is not explicitly provided. ## Subscriptions diff --git a/packages/aws-cdk-lib/aws-sns/lib/topic.ts b/packages/aws-cdk-lib/aws-sns/lib/topic.ts index 341548644e70d..31cd3b3defd7a 100644 --- a/packages/aws-cdk-lib/aws-sns/lib/topic.ts +++ b/packages/aws-cdk-lib/aws-sns/lib/topic.ts @@ -51,7 +51,7 @@ export interface TopicProps { /** * The list of delivery status logging configurations for the topic. * - * For more information, see https://docs.aws.amazon.com/sns/latest/dg/sns-topic-attributes.html. + * @see https://docs.aws.amazon.com/sns/latest/dg/sns-topic-attributes.html. * * @default None */ @@ -71,17 +71,27 @@ export interface TopicProps { /** * Adds a statement to enforce encryption of data in transit when publishing to the topic. * - * For more information, see https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit. + * @see https://docs.aws.amazon.com/sns/latest/dg/sns-security-best-practices.html#enforce-encryption-data-in-transit. * * @default false */ readonly enforceSSL?: boolean; + + /** + * The signature version corresponds to the hashing algorithm used while creating the signature of the notifications, + * subscription confirmations, or unsubscribe confirmation messages sent by Amazon SNS. + * + * @see https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html. + * + * @default 1 + */ + readonly signatureVersion?: string; } /** * A logging configuration for delivery status of messages sent from SNS topic to subscribed endpoints. * - * For more information, see https://docs.aws.amazon.com/sns/latest/dg/sns-topic-attributes.html. + * @see https://docs.aws.amazon.com/sns/latest/dg/sns-topic-attributes.html. */ export interface LoggingConfig { /** @@ -207,7 +217,7 @@ export class Topic extends TopicBase { if (props.fifo && props.topicName && !props.topicName.endsWith('.fifo')) { cfnTopicName = this.physicalName + '.fifo'; } else if (props.fifo && !props.topicName) { - // Max lenght allowed by CloudFormation is 256, we subtract 5 to allow for ".fifo" suffix + // Max length allowed by CloudFormation is 256, we subtract 5 to allow for ".fifo" suffix const prefixName = Names.uniqueResourceName(this, { maxLength: 256 - 5, separator: '-', @@ -217,6 +227,15 @@ export class Topic extends TopicBase { cfnTopicName = this.physicalName; } + if ( + props.signatureVersion && + !Token.isUnresolved(props.signatureVersion) && + props.signatureVersion !== '1' && + props.signatureVersion !== '2' + ) { + throw new Error(`signatureVersion must be "1" or "2", received: "${props.signatureVersion}"`); + } + const resource = new CfnTopic(this, 'Resource', { archivePolicy: props.messageRetentionPeriodInDays ? { MessageRetentionPeriod: props.messageRetentionPeriodInDays, @@ -226,6 +245,7 @@ export class Topic extends TopicBase { kmsMasterKeyId: props.masterKey && props.masterKey.keyArn, contentBasedDeduplication: props.contentBasedDeduplication, fifoTopic: props.fifo, + signatureVersion: props.signatureVersion, deliveryStatusLogging: Lazy.any({ produce: () => this.renderLoggingConfigs() }, { omitEmptyArray: true }), }); diff --git a/packages/aws-cdk-lib/aws-sns/test/sns.test.ts b/packages/aws-cdk-lib/aws-sns/test/sns.test.ts index 4cb1ee8a8b275..6bbb9bf0b63b3 100644 --- a/packages/aws-cdk-lib/aws-sns/test/sns.test.ts +++ b/packages/aws-cdk-lib/aws-sns/test/sns.test.ts @@ -156,6 +156,26 @@ describe('Topic', () => { })).toThrow(/Content based deduplication can only be enabled for FIFO SNS topics./); }); + + test('specify signatureVersion', () => { + const stack = new cdk.Stack(); + + new sns.Topic(stack, 'MyTopic', { + signatureVersion: '2', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::SNS::Topic', { + 'SignatureVersion': '2', + }); + }); + + test('throw with incorrect signatureVersion', () => { + const stack = new cdk.Stack(); + + expect(() => new sns.Topic(stack, 'MyTopic', { + signatureVersion: '3', + })).toThrow(/signatureVersion must be "1" or "2", received: "3"/); + }); }); test('can add a policy to the topic', () => {