Skip to content

Commit b953b2a

Browse files
authored
fix(scheduler-targets-alpha): add dlq policy to execution role instead of queue policy (#32032)
### Issue # (if applicable) Tracking #31785. ### Reason for this change Currently if a dead letter queue (DLQ) is specified then a queue policy is created for the DLQ which allows the schedule to send messages. This is incorrect and the permissions should be added to the schedule's execution role instead. ### Description of changes Add `sqs:SendMessage` permission to execution role's policy statement if dead letter queue is specified. This follows the [service docs](https://docs.aws.amazon.com/scheduler/latest/UserGuide/configuring-schedule-dlq.html#configuring-schedule-dlq-permissions) for configuring a schedule DLQ. Also removed cross-region validation as the deployment will fail fast for this case so the validation is unnecessary. ### Description of how you validated changes Updated unit tests and added a new integration test with dead letter queue setup on the schedule ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 8a3734d commit b953b2a

22 files changed

+37252
-617
lines changed

packages/@aws-cdk/aws-scheduler-targets-alpha/lib/target.ts

+8-27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { ISchedule, ScheduleTargetConfig, ScheduleTargetInput } from '@aws-cdk/aws-scheduler-alpha';
2-
import { Annotations, Duration, Names, PhysicalName, Stack, Token } from 'aws-cdk-lib';
2+
import { Duration, PhysicalName, Stack, Token } from 'aws-cdk-lib';
33
import * as iam from 'aws-cdk-lib/aws-iam';
44
import { CfnSchedule } from 'aws-cdk-lib/aws-scheduler';
55
import * as sqs from 'aws-cdk-lib/aws-sqs';
66
import { md5hash } from 'aws-cdk-lib/core/lib/helpers-internal';
7-
import { sameEnvDimension } from './util';
87

98
/**
109
* Base properties for a Schedule Target
@@ -81,7 +80,7 @@ export abstract class ScheduleTargetBase {
8180
this.addTargetActionToRole(_schedule, role);
8281

8382
if (this.baseProps.deadLetterQueue) {
84-
this.addToDeadLetterQueueResourcePolicy(_schedule, this.baseProps.deadLetterQueue);
83+
this.addDeadLetterQueueActionToRole(role, this.baseProps.deadLetterQueue);
8584
}
8685

8786
return {
@@ -148,31 +147,13 @@ export abstract class ScheduleTargetBase {
148147
}
149148

150149
/**
151-
* Allow a schedule to send events with failed invocation to an Amazon SQS queue.
152-
* @param schedule schedule to add DLQ to
153-
* @param queue the DLQ
150+
* Allow schedule to send events with failed invocation to an Amazon SQS queue.
154151
*/
155-
private addToDeadLetterQueueResourcePolicy(schedule: ISchedule, queue: sqs.IQueue) {
156-
if (!sameEnvDimension(schedule.env.region, queue.env.region)) {
157-
throw new Error(`Cannot assign Dead Letter Queue in region ${queue.env.region} to the schedule ${Names.nodeUniqueId(schedule.node)} in region ${schedule.env.region}. Both the queue and the schedule must be in the same region.`);
158-
}
159-
160-
// Skip Resource Policy creation if the Queue is not in the same account.
161-
// There is no way to add a target onto an imported schedule, so we can assume we will run the following code only
162-
// in the account where the schedule is created.
163-
if (sameEnvDimension(schedule.env.account, queue.env.account)) {
164-
const policyStatementId = `AllowSchedule${Names.nodeUniqueId(schedule.node)}`;
165-
166-
queue.addToResourcePolicy(new iam.PolicyStatement({
167-
sid: policyStatementId,
168-
principals: [new iam.ServicePrincipal('scheduler.amazonaws.com')],
169-
effect: iam.Effect.ALLOW,
170-
actions: ['sqs:SendMessage'],
171-
resources: [queue.queueArn],
172-
}));
173-
} else {
174-
Annotations.of(schedule).addWarning(`Cannot add a resource policy to your dead letter queue associated with schedule ${schedule.scheduleName} because the queue is in a different account. You must add the resource policy manually to the dead letter queue in account ${queue.env.account}.`);
175-
}
152+
private addDeadLetterQueueActionToRole(role: iam.IRole, queue: sqs.IQueue) {
153+
role.addToPrincipalPolicy(new iam.PolicyStatement({
154+
actions: ['sqs:SendMessage'],
155+
resources: [queue.queueArn],
156+
}));
176157
}
177158

178159
private renderRetryPolicy(maximumEventAge: Duration | undefined, maximumRetryAttempts: number | undefined): CfnSchedule.RetryPolicyProperty {

packages/@aws-cdk/aws-scheduler-targets-alpha/test/codebuild-start-build.test.ts

+26-53
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ describe('codebuild start build', () => {
419419
})).toThrow(/Both the target and the execution role must be in the same account/);
420420
});
421421

422-
test('adds permissions to DLQ', () => {
422+
test('adds permissions to execution role for sending messages to DLQ', () => {
423423
const dlq = new Queue(stack, 'DummyDeadLetterQueue');
424424

425425
const codebuildProjectTarget = new CodeBuildStartBuild(codebuildProject, {
@@ -431,49 +431,30 @@ describe('codebuild start build', () => {
431431
target: codebuildProjectTarget,
432432
});
433433

434-
Template.fromStack(stack).hasResourceProperties('AWS::SQS::QueuePolicy', {
434+
const template = Template.fromStack(stack);
435+
436+
template.hasResourceProperties('AWS::IAM::Policy', {
435437
PolicyDocument: {
436438
Statement: [
439+
{
440+
Action: codebuildAction,
441+
Effect: 'Allow',
442+
Resource: codebuildArnRef,
443+
},
437444
{
438445
Action: 'sqs:SendMessage',
439-
Principal: {
440-
Service: 'scheduler.amazonaws.com',
441-
},
442446
Effect: 'Allow',
443447
Resource: {
444448
'Fn::GetAtt': ['DummyDeadLetterQueueCEBF3463', 'Arn'],
445449
},
446450
},
447451
],
448452
},
449-
Queues: [
450-
{
451-
Ref: 'DummyDeadLetterQueueCEBF3463',
452-
},
453-
],
454-
});
455-
});
456-
457-
test('throws when adding permissions to DLQ from a different region', () => {
458-
const stack2 = new Stack(app, 'Stack2', {
459-
env: {
460-
region: 'eu-west-2',
461-
},
462-
});
463-
const queue = new Queue(stack2, 'DummyDeadLetterQueue');
464-
465-
const codebuildProjectTarget = new CodeBuildStartBuild(codebuildProject, {
466-
deadLetterQueue: queue,
453+
Roles: [{ Ref: roleId }],
467454
});
468-
469-
expect(() =>
470-
new Schedule(stack, 'MyScheduleDummy', {
471-
schedule: expr,
472-
target: codebuildProjectTarget,
473-
})).toThrow(/Both the queue and the schedule must be in the same region./);
474455
});
475456

476-
test('does not create a queue policy when DLQ is imported', () => {
457+
test('adds permission to execution role when imported DLQ is in same account', () => {
477458
const importedQueue = Queue.fromQueueArn(stack, 'ImportedQueue', 'arn:aws:sqs:us-east-1:123456789012:queue1');
478459

479460
const codebuildProjectTarget = new CodeBuildStartBuild(codebuildProject, {
@@ -485,31 +466,23 @@ describe('codebuild start build', () => {
485466
target: codebuildProjectTarget,
486467
});
487468

488-
Template.fromStack(stack).resourceCountIs('AWS::SQS::QueuePolicy', 0);
489-
});
490-
491-
test('does not create a queue policy when DLQ is created in a different account', () => {
492-
const stack2 = new Stack(app, 'Stack2', {
493-
env: {
494-
region: 'us-east-1',
495-
account: '234567890123',
469+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
470+
PolicyDocument: {
471+
Statement: [
472+
{
473+
Action: codebuildAction,
474+
Effect: 'Allow',
475+
Resource: codebuildArnRef,
476+
},
477+
{
478+
Action: 'sqs:SendMessage',
479+
Effect: 'Allow',
480+
Resource: importedQueue.queueArn,
481+
},
482+
],
496483
},
484+
Roles: [{ Ref: roleId }],
497485
});
498-
499-
const queue = new Queue(stack2, 'DummyDeadLetterQueue', {
500-
queueName: 'DummyDeadLetterQueue',
501-
});
502-
503-
const codebuildProjectTarget = new CodeBuildStartBuild(codebuildProject, {
504-
deadLetterQueue: queue,
505-
});
506-
507-
new Schedule(stack, 'MyScheduleDummy', {
508-
schedule: expr,
509-
target: codebuildProjectTarget,
510-
});
511-
512-
Template.fromStack(stack).resourceCountIs('AWS::SQS::QueuePolicy', 0);
513486
});
514487

515488
test('renders expected retry policy', () => {

packages/@aws-cdk/aws-scheduler-targets-alpha/test/codepipeline-start-pipeline-execution.test.ts

+24-53
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ describe('codepipeline start execution', () => {
439439
})).toThrow(/Both the target and the execution role must be in the same account/);
440440
});
441441

442-
test('adds permissions to DLQ', () => {
442+
test('adds permissions to execution role for sending messages to DLQ', () => {
443443
const dlq = new sqs.Queue(stack, 'DummyDeadLetterQueue');
444444

445445
const codepipelineTarget = new CodePipelineStartPipelineExecution(codepipeline, {
@@ -451,49 +451,28 @@ describe('codepipeline start execution', () => {
451451
target: codepipelineTarget,
452452
});
453453

454-
Template.fromStack(stack).hasResourceProperties('AWS::SQS::QueuePolicy', {
454+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
455455
PolicyDocument: {
456456
Statement: [
457+
{
458+
Action: 'codepipeline:StartPipelineExecution',
459+
Effect: 'Allow',
460+
Resource: pipelineArn,
461+
},
457462
{
458463
Action: 'sqs:SendMessage',
459-
Principal: {
460-
Service: 'scheduler.amazonaws.com',
461-
},
462464
Effect: 'Allow',
463465
Resource: {
464466
'Fn::GetAtt': ['DummyDeadLetterQueueCEBF3463', 'Arn'],
465467
},
466468
},
467469
],
468470
},
469-
Queues: [
470-
{
471-
Ref: 'DummyDeadLetterQueueCEBF3463',
472-
},
473-
],
474-
});
475-
});
476-
477-
test('throws when adding permissions to DLQ from a different region', () => {
478-
const stack2 = new Stack(app, 'Stack2', {
479-
env: {
480-
region: 'eu-west-2',
481-
},
482-
});
483-
const queue = new sqs.Queue(stack2, 'DummyDeadLetterQueue');
484-
485-
const codepipelineTarget = new CodePipelineStartPipelineExecution(codepipeline, {
486-
deadLetterQueue: queue,
471+
Roles: [{ Ref: roleId }],
487472
});
488-
489-
expect(() =>
490-
new Schedule(stack, 'MyScheduleDummy', {
491-
schedule: expr,
492-
target: codepipelineTarget,
493-
})).toThrow(/Both the queue and the schedule must be in the same region./);
494473
});
495474

496-
test('does not create a queue policy when DLQ is imported', () => {
475+
test('adds permission to execution role when imported DLQ is in same account', () => {
497476
const importedQueue = sqs.Queue.fromQueueArn(stack, 'ImportedQueue', 'arn:aws:sqs:us-east-1:123456789012:queue1');
498477

499478
const codepipelineTarget = new CodePipelineStartPipelineExecution(codepipeline, {
@@ -505,31 +484,23 @@ describe('codepipeline start execution', () => {
505484
target: codepipelineTarget,
506485
});
507486

508-
Template.fromStack(stack).resourceCountIs('AWS::SQS::QueuePolicy', 0);
509-
});
510-
511-
test('does not create a queue policy when DLQ is created in a different account', () => {
512-
const stack2 = new Stack(app, 'Stack2', {
513-
env: {
514-
region: 'us-east-1',
515-
account: '234567890123',
487+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
488+
PolicyDocument: {
489+
Statement: [
490+
{
491+
Action: 'codepipeline:StartPipelineExecution',
492+
Effect: 'Allow',
493+
Resource: pipelineArn,
494+
},
495+
{
496+
Action: 'sqs:SendMessage',
497+
Effect: 'Allow',
498+
Resource: importedQueue.queueArn,
499+
},
500+
],
516501
},
502+
Roles: [{ Ref: roleId }],
517503
});
518-
519-
const queue = new sqs.Queue(stack2, 'DummyDeadLetterQueue', {
520-
queueName: 'DummyDeadLetterQueue',
521-
});
522-
523-
const codepipelineTarget = new CodePipelineStartPipelineExecution(codepipeline, {
524-
deadLetterQueue: queue,
525-
});
526-
527-
new Schedule(stack, 'MyScheduleDummy', {
528-
schedule: expr,
529-
target: codepipelineTarget,
530-
});
531-
532-
Template.fromStack(stack).resourceCountIs('AWS::SQS::QueuePolicy', 0);
533504
});
534505

535506
test('renders expected retry policy', () => {

0 commit comments

Comments
 (0)