Skip to content

Commit ccc2e30

Browse files
authored
feat(sns): add sns service trust to keys for encrypted queue subscriptions (#14960)
Add SNS service trust to KMS key when creating an SNS subscription for an encrypted SQS queue. Closes #2504 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5c9363d commit ccc2e30

File tree

4 files changed

+169
-2
lines changed

4 files changed

+169
-2
lines changed

packages/@aws-cdk/aws-sns-subscriptions/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ const myQueue = new sqs.Queue(this, 'MyQueue');
6565
myTopic.addSubscription(new subscriptions.SqsSubscription(queue));
6666
```
6767

68+
KMS key permissions will automatically be granted to SNS when a subscription is made to
69+
an encrypted queue.
70+
6871
Note that subscriptions of queues in different accounts need to be manually confirmed by
6972
reading the initial message from the queue and visiting the link found in it.
7073

packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,29 @@ export class SqsSubscription implements sns.ITopicSubscription {
3838
if (!Construct.isConstruct(this.queue)) {
3939
throw new Error('The supplied Queue object must be an instance of Construct');
4040
}
41+
const snsServicePrincipal = new iam.ServicePrincipal('sns.amazonaws.com');
4142

4243
// add a statement to the queue resource policy which allows this topic
4344
// to send messages to the queue.
4445
this.queue.addToResourcePolicy(new iam.PolicyStatement({
4546
resources: [this.queue.queueArn],
4647
actions: ['sqs:SendMessage'],
47-
principals: [new iam.ServicePrincipal('sns.amazonaws.com')],
48+
principals: [snsServicePrincipal],
4849
conditions: {
4950
ArnEquals: { 'aws:SourceArn': topic.topicArn },
5051
},
5152
}));
5253

54+
// if the queue is encrypted, add a statement to the key resource policy
55+
// which allows this topic to decrypt KMS keys
56+
if (this.queue.encryptionMasterKey) {
57+
this.queue.encryptionMasterKey.addToResourcePolicy(new iam.PolicyStatement({
58+
resources: ['*'],
59+
actions: ['kms:Decrypt', 'kms:GenerateDataKey'],
60+
principals: [snsServicePrincipal],
61+
}));
62+
}
63+
5364
return {
5465
subscriberScope: this.queue,
5566
subscriberId: Names.nodeUniqueId(topic.node),

packages/@aws-cdk/aws-sns-subscriptions/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
},
7575
"dependencies": {
7676
"@aws-cdk/aws-iam": "0.0.0",
77+
"@aws-cdk/aws-kms": "0.0.0",
7778
"@aws-cdk/aws-lambda": "0.0.0",
7879
"@aws-cdk/aws-sns": "0.0.0",
7980
"@aws-cdk/aws-sqs": "0.0.0",
@@ -83,6 +84,7 @@
8384
"homepage": "https://github.com/aws/aws-cdk",
8485
"peerDependencies": {
8586
"@aws-cdk/aws-iam": "0.0.0",
87+
"@aws-cdk/aws-kms": "0.0.0",
8688
"@aws-cdk/aws-lambda": "0.0.0",
8789
"@aws-cdk/aws-sns": "0.0.0",
8890
"@aws-cdk/aws-sqs": "0.0.0",

packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts

+152-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import '@aws-cdk/assert-internal/jest';
2+
import * as kms from '@aws-cdk/aws-kms';
23
import * as lambda from '@aws-cdk/aws-lambda';
34
import * as sns from '@aws-cdk/aws-sns';
45
import * as sqs from '@aws-cdk/aws-sqs';
5-
import { CfnParameter, Duration, Stack, Token } from '@aws-cdk/core';
6+
import { CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core';
67
import * as subs from '../lib';
78

89
/* eslint-disable quote-props */
@@ -458,6 +459,156 @@ test('queue subscription (with raw delivery)', () => {
458459
});
459460
});
460461

462+
test('encrypted queue subscription', () => {
463+
const key = new kms.Key(stack, 'MyKey', {
464+
removalPolicy: RemovalPolicy.DESTROY,
465+
});
466+
467+
const queue = new sqs.Queue(stack, 'MyQueue', {
468+
encryption: sqs.QueueEncryption.KMS,
469+
encryptionMasterKey: key,
470+
});
471+
472+
topic.addSubscription(new subs.SqsSubscription(queue));
473+
474+
expect(stack).toMatchTemplate({
475+
'Resources': {
476+
'MyTopic86869434': {
477+
'Type': 'AWS::SNS::Topic',
478+
'Properties': {
479+
'DisplayName': 'displayName',
480+
'TopicName': 'topicName',
481+
},
482+
},
483+
'MyKey6AB29FA6': {
484+
'Type': 'AWS::KMS::Key',
485+
'Properties': {
486+
'KeyPolicy': {
487+
'Statement': [
488+
{
489+
'Action': [
490+
'kms:Create*',
491+
'kms:Describe*',
492+
'kms:Enable*',
493+
'kms:List*',
494+
'kms:Put*',
495+
'kms:Update*',
496+
'kms:Revoke*',
497+
'kms:Disable*',
498+
'kms:Get*',
499+
'kms:Delete*',
500+
'kms:ScheduleKeyDeletion',
501+
'kms:CancelKeyDeletion',
502+
'kms:GenerateDataKey',
503+
'kms:TagResource',
504+
'kms:UntagResource',
505+
],
506+
'Effect': 'Allow',
507+
'Principal': {
508+
'AWS': {
509+
'Fn::Join': [
510+
'',
511+
[
512+
'arn:',
513+
{
514+
'Ref': 'AWS::Partition',
515+
},
516+
':iam::',
517+
{
518+
'Ref': 'AWS::AccountId',
519+
},
520+
':root',
521+
],
522+
],
523+
},
524+
},
525+
'Resource': '*',
526+
},
527+
{
528+
'Action': [
529+
'kms:Decrypt',
530+
'kms:GenerateDataKey',
531+
],
532+
'Effect': 'Allow',
533+
'Principal': {
534+
'Service': 'sns.amazonaws.com',
535+
},
536+
'Resource': '*',
537+
},
538+
],
539+
'Version': '2012-10-17',
540+
},
541+
},
542+
'UpdateReplacePolicy': 'Delete',
543+
'DeletionPolicy': 'Delete',
544+
},
545+
'MyQueueE6CA6235': {
546+
'Type': 'AWS::SQS::Queue',
547+
'Properties': {
548+
'KmsMasterKeyId': {
549+
'Fn::GetAtt': [
550+
'MyKey6AB29FA6',
551+
'Arn',
552+
],
553+
},
554+
},
555+
'DeletionPolicy': 'Delete',
556+
'UpdateReplacePolicy': 'Delete',
557+
},
558+
'MyQueuePolicy6BBEDDAC': {
559+
'Type': 'AWS::SQS::QueuePolicy',
560+
'Properties': {
561+
'PolicyDocument': {
562+
'Statement': [
563+
{
564+
'Action': 'sqs:SendMessage',
565+
'Condition': {
566+
'ArnEquals': {
567+
'aws:SourceArn': {
568+
'Ref': 'MyTopic86869434',
569+
},
570+
},
571+
},
572+
'Effect': 'Allow',
573+
'Principal': {
574+
'Service': 'sns.amazonaws.com',
575+
},
576+
'Resource': {
577+
'Fn::GetAtt': [
578+
'MyQueueE6CA6235',
579+
'Arn',
580+
],
581+
},
582+
},
583+
],
584+
'Version': '2012-10-17',
585+
},
586+
'Queues': [
587+
{
588+
'Ref': 'MyQueueE6CA6235',
589+
},
590+
],
591+
},
592+
},
593+
'MyQueueMyTopic9B00631B': {
594+
'Type': 'AWS::SNS::Subscription',
595+
'Properties': {
596+
'Protocol': 'sqs',
597+
'TopicArn': {
598+
'Ref': 'MyTopic86869434',
599+
},
600+
'Endpoint': {
601+
'Fn::GetAtt': [
602+
'MyQueueE6CA6235',
603+
'Arn',
604+
],
605+
},
606+
},
607+
},
608+
},
609+
});
610+
});
611+
461612
test('lambda subscription', () => {
462613
const fction = new lambda.Function(stack, 'MyFunc', {
463614
runtime: lambda.Runtime.NODEJS_10_X,

0 commit comments

Comments
 (0)