Skip to content

Commit 74318c7

Browse files
authored
fix(events-targets): EventBus IAM statements are only added for the first target (#20479)
If the `EventBus` constructor is called with no arguments, then attaching more than a single target to its policy will silently fail to add them. This is because of a strange edge case in the implementation that was not accounted for previously; it is possible for `props.role` to be `undefined`, yet `singletonEventRole()` is still capable of finding the desired role. `singletonEventRole()` does not add the new statements to any IAM policies that it finds, so as a result adding multiple targets does not add any of them. Fixes #19407. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 264c02e commit 74318c7

File tree

12 files changed

+138
-55
lines changed

12 files changed

+138
-55
lines changed

packages/@aws-cdk/aws-events-targets/lib/api-destination.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,16 @@ export class ApiDestination implements events.IRuleTarget {
8383
addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue);
8484
}
8585

86+
const role = this.props?.eventRole ?? singletonEventRole(this.apiDestination);
87+
role.addToPrincipalPolicy(new iam.PolicyStatement({
88+
resources: [this.apiDestination.apiDestinationArn],
89+
actions: ['events:InvokeApiDestination'],
90+
}));
91+
8692
return {
8793
...(this.props ? bindBaseTargetConfig(this.props) : {}),
8894
arn: this.apiDestination.apiDestinationArn,
89-
role: this.props?.eventRole ?? singletonEventRole(this.apiDestination, [new iam.PolicyStatement({
90-
resources: [this.apiDestination.apiDestinationArn],
91-
actions: ['events:InvokeApiDestination'],
92-
})]),
95+
role,
9396
input: this.props.event,
9497
targetResource: this.apiDestination,
9598
httpParameters,

packages/@aws-cdk/aws-events-targets/lib/api-gateway.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,20 @@ export class ApiGateway implements events.IRuleTarget {
9898
this.props?.path || '/',
9999
this.props?.stage || this.restApi.deploymentStage.stageName,
100100
);
101+
102+
const role = this.props?.eventRole || singletonEventRole(this.restApi);
103+
role.addToPrincipalPolicy(new iam.PolicyStatement({
104+
resources: [restApiArn],
105+
actions: [
106+
'execute-api:Invoke',
107+
'execute-api:ManageConnections',
108+
],
109+
}));
110+
101111
return {
102112
...(this.props ? bindBaseTargetConfig(this.props) : {}),
103113
arn: restApiArn,
104-
role: this.props?.eventRole || singletonEventRole(this.restApi, [new iam.PolicyStatement({
105-
resources: [restApiArn],
106-
actions: [
107-
'execute-api:Invoke',
108-
'execute-api:ManageConnections',
109-
],
110-
})]),
114+
role,
111115
deadLetterConfig: this.props?.deadLetterQueue && { arn: this.props.deadLetterQueue?.queueArn },
112116
input: this.props?.postBody,
113117
targetResource: this.restApi,

packages/@aws-cdk/aws-events-targets/lib/batch.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,21 @@ export class BatchJob implements events.IRuleTarget {
8787
addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue);
8888
}
8989

90+
// When scoping resource-level access for job submission, you must provide both job queue and job definition resource types.
91+
// https://docs.aws.amazon.com/batch/latest/userguide/ExamplePolicies_BATCH.html#iam-example-restrict-job-def
92+
const role = singletonEventRole(this.jobDefinitionScope);
93+
role.addToPrincipalPolicy(new iam.PolicyStatement({
94+
actions: ['batch:SubmitJob'],
95+
resources: [
96+
this.jobDefinitionArn,
97+
this.jobQueueArn,
98+
],
99+
}));
100+
90101
return {
91102
...bindBaseTargetConfig(this.props),
92103
arn: this.jobQueueArn,
93-
// When scoping resource-level access for job submission, you must provide both job queue and job definition resource types.
94-
// https://docs.aws.amazon.com/batch/latest/userguide/ExamplePolicies_BATCH.html#iam-example-restrict-job-def
95-
role: singletonEventRole(this.jobDefinitionScope, [
96-
new iam.PolicyStatement({
97-
actions: ['batch:SubmitJob'],
98-
resources: [
99-
this.jobDefinitionArn,
100-
this.jobQueueArn,
101-
],
102-
}),
103-
]),
104+
role,
104105
input: this.props.event,
105106
targetResource: this.jobQueueScope,
106107
batchParameters,

packages/@aws-cdk/aws-events-targets/lib/codebuild.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ export class CodeBuildProject implements events.IRuleTarget {
4444
addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue);
4545
}
4646

47+
const role = this.props.eventRole || singletonEventRole(this.project);
48+
role.addToPrincipalPolicy(new iam.PolicyStatement({
49+
actions: ['codebuild:StartBuild'],
50+
resources: [this.project.projectArn],
51+
}));
52+
4753
return {
4854
...bindBaseTargetConfig(this.props),
4955
arn: this.project.projectArn,
50-
role: this.props.eventRole || singletonEventRole(this.project, [
51-
new iam.PolicyStatement({
52-
actions: ['codebuild:StartBuild'],
53-
resources: [this.project.projectArn],
54-
}),
55-
]),
56+
role,
5657
input: this.props.event,
5758
targetResource: this.project,
5859
};

packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ export class CodePipeline implements events.IRuleTarget {
2626
}
2727

2828
public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig {
29+
const role = this.options.eventRole || singletonEventRole(this.pipeline);
30+
role.addToPrincipalPolicy(new iam.PolicyStatement({
31+
resources: [this.pipeline.pipelineArn],
32+
actions: ['codepipeline:StartPipelineExecution'],
33+
}));
34+
2935
return {
3036
...bindBaseTargetConfig(this.options),
3137
id: '',
3238
arn: this.pipeline.pipelineArn,
33-
role: this.options.eventRole || singletonEventRole(this.pipeline, [new iam.PolicyStatement({
34-
resources: [this.pipeline.pipelineArn],
35-
actions: ['codepipeline:StartPipelineExecution'],
36-
})]),
39+
role,
3740
targetResource: this.pipeline,
3841
};
3942
}

packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,9 @@ export class EcsTask implements events.IRuleTarget {
118118
this.taskCount = props.taskCount ?? 1;
119119
this.platformVersion = props.platformVersion;
120120

121-
if (props.role) {
122-
const role = props.role;
123-
this.createEventRolePolicyStatements().forEach(role.addToPrincipalPolicy.bind(role));
124-
this.role = role;
125-
} else {
126-
this.role = singletonEventRole(this.taskDefinition, this.createEventRolePolicyStatements());
121+
this.role = props.role ?? singletonEventRole(this.taskDefinition);
122+
for (const stmt of this.createEventRolePolicyStatements()) {
123+
this.role.addToPrincipalPolicy(stmt);
127124
}
128125

129126
// Security groups are only configurable with the "awsvpc" network mode.

packages/@aws-cdk/aws-events-targets/lib/event-bus.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,8 @@ export class EventBus implements events.IRuleTarget {
3636
constructor(private readonly eventBus: events.IEventBus, private readonly props: EventBusProps = {}) { }
3737

3838
bind(rule: events.IRule, _id?: string): events.RuleTargetConfig {
39-
if (this.props.role) {
40-
this.props.role.addToPrincipalPolicy(this.putEventStatement());
41-
}
42-
const role = this.props.role ?? singletonEventRole(rule, [this.putEventStatement()]);
39+
const role = this.props.role ?? singletonEventRole(rule);
40+
role.addToPrincipalPolicy(this.putEventStatement());
4341

4442
if (this.props.deadLetterQueue) {
4543
addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue);

packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ export class KinesisFirehoseStream implements events.IRuleTarget {
3131
* result from a Event Bridge event.
3232
*/
3333
public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig {
34-
const policyStatements = [new iam.PolicyStatement({
34+
const role = singletonEventRole(this.stream);
35+
role.addToPrincipalPolicy(new iam.PolicyStatement({
3536
actions: ['firehose:PutRecord', 'firehose:PutRecordBatch'],
3637
resources: [this.stream.attrArn],
37-
})];
38+
}));
39+
3840

3941
return {
4042
arn: this.stream.attrArn,
41-
role: singletonEventRole(this.stream, policyStatements),
43+
role,
4244
input: this.props.message,
4345
targetResource: this.stream,
4446
};

packages/@aws-cdk/aws-events-targets/lib/kinesis-stream.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ export class KinesisStream implements events.IRuleTarget {
4545
* result from a CloudWatch event.
4646
*/
4747
public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig {
48-
const policyStatements = [new iam.PolicyStatement({
48+
const role = singletonEventRole(this.stream);
49+
role.addToPrincipalPolicy(new iam.PolicyStatement({
4950
actions: ['kinesis:PutRecord', 'kinesis:PutRecords'],
5051
resources: [this.stream.streamArn],
51-
})];
52+
}));
5253

5354
return {
5455
arn: this.stream.streamArn,
55-
role: singletonEventRole(this.stream, policyStatements),
56+
role,
5657
input: this.props.message,
5758
targetResource: this.stream,
5859
kinesisParameters: this.props.partitionKeyPath ? { partitionKeyPath: this.props.partitionKeyPath } : undefined,

packages/@aws-cdk/aws-events-targets/lib/state-machine.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,8 @@ export class SfnStateMachine implements events.IRuleTarget {
2929
private readonly role: iam.IRole;
3030

3131
constructor(public readonly machine: sfn.IStateMachine, private readonly props: SfnStateMachineProps = {}) {
32-
if (props.role) {
33-
props.role.grant(new iam.ServicePrincipal('events.amazonaws.com'));
34-
}
3532
// no statements are passed because we are configuring permissions by using grant* helper below
36-
this.role = props.role ?? singletonEventRole(machine, []);
33+
this.role = props.role ?? singletonEventRole(machine);
3734
machine.grantStartExecution(this.role);
3835
}
3936

0 commit comments

Comments
 (0)