Skip to content

Commit 0531492

Browse files
authored
fix(sns-subscriptions): SQS queue encrypted by AWS managed KMS key is allowed to be specified as subscription and dead-letter queue (#26110)
To send message to SQS queues encrypted by KMS from SNS, we need to grant SNS service-principal access to the key by key policy. From this reason, we need to use customer managed key because we can't edit key policy for AWS managed key. However, CDK makes it easy to create such a non-functional subscription. To prevent CDK from making such a subscription, I added the validation which throw an error when SQS queue encrypted by AWS managed KMS key is specified as subscription or dead-letter queue. Closes #19796 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6033c9a commit 0531492

File tree

5 files changed

+121
-35
lines changed

5 files changed

+121
-35
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ export class SqsSubscription implements sns.ITopicSubscription {
3838
}
3939
const snsServicePrincipal = new iam.ServicePrincipal('sns.amazonaws.com');
4040

41+
// if the queue is encrypted by AWS managed KMS key (alias/aws/sqs),
42+
// throw error message
43+
if (this.queue.encryptionType === sqs.QueueEncryption.KMS_MANAGED) {
44+
throw new Error('SQS queue encrypted by AWS managed KMS key cannot be used as SNS subscription');
45+
}
46+
47+
// if the dead-letter queue is encrypted by AWS managed KMS key (alias/aws/sqs),
48+
// throw error message
49+
if (this.props.deadLetterQueue && this.props.deadLetterQueue.encryptionType === sqs.QueueEncryption.KMS_MANAGED) {
50+
throw new Error('SQS queue encrypted by AWS managed KMS key cannot be used as dead-letter queue');
51+
}
52+
4153
// add a statement to the queue resource policy which allows this topic
4254
// to send messages to the queue.
4355
const queuePolicyDependable = this.queue.addToResourcePolicy(new iam.PolicyStatement({

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

+40
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,46 @@ describe('Restrict sqs decryption feature flag', () => {
11931193
});
11941194
});
11951195

1196+
test('throws an error when a queue is encrypted by AWS managed KMS kye for queue subscription', () => {
1197+
// WHEN
1198+
const queue = new sqs.Queue(stack, 'MyQueue', {
1199+
encryption: sqs.QueueEncryption.KMS_MANAGED,
1200+
});
1201+
1202+
// THEN
1203+
expect(() => topic.addSubscription(new subs.SqsSubscription(queue)))
1204+
.toThrowError(/SQS queue encrypted by AWS managed KMS key cannot be used as SNS subscription/);
1205+
});
1206+
1207+
test('throws an error when a dead-letter queue is encrypted by AWS managed KMS kye for queue subscription', () => {
1208+
// WHEN
1209+
const queue = new sqs.Queue(stack, 'MyQueue');
1210+
const dlq = new sqs.Queue(stack, 'MyDLQ', {
1211+
encryption: sqs.QueueEncryption.KMS_MANAGED,
1212+
});
1213+
1214+
// THEN
1215+
expect(() => topic.addSubscription(new subs.SqsSubscription(queue, {
1216+
deadLetterQueue: dlq,
1217+
})))
1218+
.toThrowError(/SQS queue encrypted by AWS managed KMS key cannot be used as dead-letter queue/);
1219+
});
1220+
1221+
test('importing SQS queue and specify this as subscription', () => {
1222+
// WHEN
1223+
const queue = sqs.Queue.fromQueueArn(stack, 'Queue', 'arn:aws:sqs:us-east-1:123456789012:queue1');
1224+
topic.addSubscription(new subs.SqsSubscription(queue));
1225+
1226+
// THEN
1227+
Template.fromStack(stack).hasResourceProperties('AWS::SNS::Subscription', {
1228+
'Endpoint': 'arn:aws:sqs:us-east-1:123456789012:queue1',
1229+
'Protocol': 'sqs',
1230+
'TopicArn': {
1231+
'Ref': 'MyTopic86869434',
1232+
},
1233+
});
1234+
});
1235+
11961236
test('lambda subscription', () => {
11971237
const func = new lambda.Function(stack, 'MyFunc', {
11981238
runtime: lambda.Runtime.NODEJS_14_X,

packages/aws-cdk-lib/aws-sqs/lib/queue-base.ts

+40
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ export interface IQueue extends IResource {
3636
*/
3737
readonly fifo: boolean;
3838

39+
/**
40+
* Whether the contents of the queue are encrypted, and by what type of key.
41+
*/
42+
readonly encryptionType?: QueueEncryption;
43+
3944
/**
4045
* Adds a statement to the IAM resource policy associated with this queue.
4146
*
@@ -126,6 +131,11 @@ export abstract class QueueBase extends Resource implements IQueue {
126131
*/
127132
public abstract readonly fifo: boolean;
128133

134+
/**
135+
* Whether the contents of the queue are encrypted, and by what type of key.
136+
*/
137+
public abstract readonly encryptionType?: QueueEncryption;
138+
129139
/**
130140
* Controls automatic creation of policy objects.
131141
*
@@ -301,3 +311,33 @@ export interface QueueAttributes {
301311
*/
302312
readonly fifo?: boolean;
303313
}
314+
315+
/**
316+
* What kind of encryption to apply to this queue
317+
*/
318+
export enum QueueEncryption {
319+
/**
320+
* Messages in the queue are not encrypted
321+
*/
322+
UNENCRYPTED = 'NONE',
323+
324+
/**
325+
* Server-side KMS encryption with a KMS key managed by SQS.
326+
*/
327+
KMS_MANAGED = 'KMS_MANAGED',
328+
329+
/**
330+
* Server-side encryption with a KMS key managed by the user.
331+
*
332+
* If `encryptionKey` is specified, this key will be used, otherwise, one will be defined.
333+
*/
334+
KMS = 'KMS',
335+
336+
/**
337+
* Server-side encryption key managed by SQS (SSE-SQS).
338+
*
339+
* To learn more about SSE-SQS on Amazon SQS, please visit the
340+
* [Amazon SQS documentation](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-server-side-encryption.html).
341+
*/
342+
SQS_MANAGED = 'SQS_MANAGED'
343+
}

packages/aws-cdk-lib/aws-sqs/lib/queue.ts

+10-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Construct } from 'constructs';
2-
import { IQueue, QueueAttributes, QueueBase } from './queue-base';
2+
import { IQueue, QueueAttributes, QueueBase, QueueEncryption } from './queue-base';
33
import { CfnQueue } from './sqs.generated';
44
import { validateProps } from './validate-props';
55
import * as iam from '../../aws-iam';
@@ -195,36 +195,6 @@ export interface DeadLetterQueue {
195195
readonly maxReceiveCount: number;
196196
}
197197

198-
/**
199-
* What kind of encryption to apply to this queue
200-
*/
201-
export enum QueueEncryption {
202-
/**
203-
* Messages in the queue are not encrypted
204-
*/
205-
UNENCRYPTED = 'NONE',
206-
207-
/**
208-
* Server-side KMS encryption with a KMS key managed by SQS.
209-
*/
210-
KMS_MANAGED = 'KMS_MANAGED',
211-
212-
/**
213-
* Server-side encryption with a KMS key managed by the user.
214-
*
215-
* If `encryptionKey` is specified, this key will be used, otherwise, one will be defined.
216-
*/
217-
KMS = 'KMS',
218-
219-
/**
220-
* Server-side encryption key managed by SQS (SSE-SQS).
221-
*
222-
* To learn more about SSE-SQS on Amazon SQS, please visit the
223-
* [Amazon SQS documentation](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-server-side-encryption.html).
224-
*/
225-
SQS_MANAGED = 'SQS_MANAGED'
226-
}
227-
228198
/**
229199
* What kind of deduplication scope to apply
230200
*/
@@ -286,6 +256,9 @@ export class Queue extends QueueBase {
286256
? kms.Key.fromKeyArn(this, 'Key', attrs.keyArn)
287257
: undefined;
288258
public readonly fifo: boolean = this.determineFifo();
259+
public readonly encryptionType = attrs.keyArn
260+
? QueueEncryption.KMS
261+
: undefined;
289262

290263
protected readonly autoCreatePolicy = false;
291264

@@ -337,6 +310,11 @@ export class Queue extends QueueBase {
337310
*/
338311
public readonly fifo: boolean;
339312

313+
/**
314+
* Whether the contents of the queue are encrypted, and by what type of key.
315+
*/
316+
public readonly encryptionType?: QueueEncryption;
317+
340318
/**
341319
* If this queue is configured with a dead-letter queue, this is the dead-letter queue settings.
342320
*/
@@ -384,6 +362,7 @@ export class Queue extends QueueBase {
384362
this.encryptionMasterKey = encryptionMasterKey;
385363
this.queueUrl = queue.ref;
386364
this.deadLetterQueue = props.deadLetterQueue;
365+
this.encryptionType = props.encryption;
387366

388367
function _determineEncryptionProps(this: Queue): { encryptionProps: EncryptionProps, encryptionMasterKey?: kms.IKey } {
389368
let encryption = props.encryption;

packages/aws-cdk-lib/aws-sqs/test/sqs.test.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ describe('export and import', () => {
182182
expect(fifoQueue.fifo).toEqual(true);
183183
});
184184

185+
test('importing with keyArn and encryptionType is set correctly', () => {
186+
const stack = new Stack();
187+
const queue = sqs.Queue.fromQueueAttributes(stack, 'Queue', {
188+
queueArn: 'arn:aws:sqs:us-east-1:123456789012:queue1',
189+
keyArn: 'arn:aws:kms:us-east-1:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab',
190+
});
191+
expect(queue.encryptionType).toEqual(sqs.QueueEncryption.KMS);
192+
});
193+
185194
test('import queueArn from token, fifo and standard queues can be defined', () => {
186195
// GIVEN
187196
const stack = new Stack();
@@ -357,7 +366,7 @@ describe('queue encryption', () => {
357366
test('a kms key will be allocated if encryption = kms but a master key is not specified', () => {
358367
const stack = new Stack();
359368

360-
new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS });
369+
const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS });
361370

362371
Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', Match.anyValue());
363372
Template.fromStack(stack).hasResourceProperties('AWS::SQS::Queue', {
@@ -368,12 +377,14 @@ describe('queue encryption', () => {
368377
],
369378
},
370379
});
380+
expect(queue.encryptionType).toEqual(sqs.QueueEncryption.KMS);
371381
});
372382

373383
test('it is possible to use a managed kms key', () => {
374384
const stack = new Stack();
375385

376-
new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS_MANAGED });
386+
const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS_MANAGED });
387+
377388
Template.fromStack(stack).templateMatches({
378389
'Resources': {
379390
'Queue4A7E3555': {
@@ -386,6 +397,7 @@ describe('queue encryption', () => {
386397
},
387398
},
388399
});
400+
expect(queue.encryptionType).toEqual(sqs.QueueEncryption.KMS_MANAGED);
389401
});
390402

391403
test('grant also affects key on encrypted queue', () => {
@@ -433,7 +445,8 @@ describe('queue encryption', () => {
433445
test('it is possible to use sqs managed server side encryption', () => {
434446
const stack = new Stack();
435447

436-
new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.SQS_MANAGED });
448+
const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.SQS_MANAGED });
449+
437450
Template.fromStack(stack).templateMatches({
438451
'Resources': {
439452
'Queue4A7E3555': {
@@ -446,12 +459,13 @@ describe('queue encryption', () => {
446459
},
447460
},
448461
});
462+
expect(queue.encryptionType).toEqual(sqs.QueueEncryption.SQS_MANAGED);
449463
});
450464

451465
test('it is possible to disable encryption (unencrypted)', () => {
452466
const stack = new Stack();
453467

454-
new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.UNENCRYPTED });
468+
const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.UNENCRYPTED });
455469
Template.fromStack(stack).templateMatches({
456470
'Resources': {
457471
'Queue4A7E3555': {
@@ -464,6 +478,7 @@ describe('queue encryption', () => {
464478
},
465479
},
466480
});
481+
expect(queue.encryptionType).toEqual(sqs.QueueEncryption.UNENCRYPTED);
467482
});
468483

469484
test('encryptionMasterKey is not supported if encryption type SQS_MANAGED is used', () => {

0 commit comments

Comments
 (0)