Skip to content

Commit 71986ff

Browse files
authored
fix(s3): add bucket policy dependency to notification resource (#30053)
### Issue Closes #27600. Closes #16811. ### Reason for this change PutBucketPolicy and PutBucketNotification API calls are happening at the same time and causing race condition because S3 does not allow parallel bucket edits. ### Description of changes Add dependency on bucket policy to custom resource so that PutBucketPolicy API call is always executed before PutBucketNotification. ### Description of how you validated changes I've reproduced the bug locally and this solution fixed it. ### 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 5ce1b64 commit 71986ff

File tree

9 files changed

+207
-7
lines changed

9 files changed

+207
-7
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.s3.js.snapshot/lambda-event-source-s3.template.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@
174174
"Managed": true
175175
},
176176
"DependsOn": [
177-
"BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709"
177+
"BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709",
178+
"BPolicy3F02723E"
178179
]
179180
},
180181
"BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709": {

packages/@aws-cdk-testing/framework-integ/test/aws-s3-notifications/test/sqs/integ.bucket-notifications.js.snapshot/sqs-bucket-notifications.template.json

+1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@
325325
"Managed": true
326326
},
327327
"DependsOn": [
328+
"Bucket2Policy945B22E3",
328329
"MyQueuePolicy6BBEDDAC",
329330
"MyQueueE6CA6235"
330331
]

packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket.notifications.js.snapshot/aws-cdk-s3-notifications.assets.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket.notifications.js.snapshot/aws-cdk-s3-notifications.template.json

+51-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,53 @@
55
"UpdateReplacePolicy": "Delete",
66
"DeletionPolicy": "Delete"
77
},
8+
"MyEventBridgeBucketPolicy8F5346E3": {
9+
"Type": "AWS::S3::BucketPolicy",
10+
"Properties": {
11+
"Bucket": {
12+
"Ref": "MyEventBridgeBucket1ABD5C2A"
13+
},
14+
"PolicyDocument": {
15+
"Statement": [
16+
{
17+
"Action": "s3:*",
18+
"Condition": {
19+
"Bool": {
20+
"aws:SecureTransport": "false"
21+
}
22+
},
23+
"Effect": "Deny",
24+
"Principal": {
25+
"AWS": "*"
26+
},
27+
"Resource": [
28+
{
29+
"Fn::GetAtt": [
30+
"MyEventBridgeBucket1ABD5C2A",
31+
"Arn"
32+
]
33+
},
34+
{
35+
"Fn::Join": [
36+
"",
37+
[
38+
{
39+
"Fn::GetAtt": [
40+
"MyEventBridgeBucket1ABD5C2A",
41+
"Arn"
42+
]
43+
},
44+
"/*"
45+
]
46+
]
47+
}
48+
]
49+
}
50+
],
51+
"Version": "2012-10-17"
52+
}
53+
}
54+
},
855
"MyEventBridgeBucketNotifications19C0453F": {
956
"Type": "Custom::S3BucketNotifications",
1057
"Properties": {
@@ -21,7 +68,10 @@
2168
"EventBridgeConfiguration": {}
2269
},
2370
"Managed": true
24-
}
71+
},
72+
"DependsOn": [
73+
"MyEventBridgeBucketPolicy8F5346E3"
74+
]
2575
},
2676
"BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": {
2777
"Type": "AWS::IAM::Role",

packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket.notifications.js.snapshot/manifest.json

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket.notifications.js.snapshot/tree.json

+65
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket.notifications.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-s3-notifications');
99

1010
new s3.Bucket(stack, 'MyEventBridgeBucket', {
1111
eventBridgeEnabled: true,
12+
enforceSSL: true, // Adding dummy bucket policy for testing that bucket policy is created before bucket notification
1213
removalPolicy: cdk.RemovalPolicy.DESTROY,
1314
});
1415

packages/aws-cdk-lib/aws-s3/lib/notifications-resource/notifications-resource.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Construct } from 'constructs';
1+
import { Construct, IConstruct } from 'constructs';
22
import { NotificationsResourceHandler } from './notifications-resource-handler';
33
import * as iam from '../../../aws-iam';
44
import * as cdk from '../../../core';
@@ -135,6 +135,20 @@ export class BucketNotifications extends Construct {
135135
Managed: managed,
136136
},
137137
});
138+
139+
// Add dependency on bucket policy if it exists to avoid race conditions
140+
// S3 does not allow calling PutBucketPolicy and PutBucketNotification APIs at the same time
141+
// See https://github.com/aws/aws-cdk/issues/27600
142+
// Aspects are used here because bucket policy maybe added to construct after addition of notification resource.
143+
const bucket = this.bucket;
144+
const resource = this.resource;
145+
cdk.Aspects.of(this).add({
146+
visit(node: IConstruct) {
147+
if (node === resource && bucket.policy) {
148+
node.node.addDependency(bucket.policy);
149+
}
150+
},
151+
});
138152
}
139153

140154
return this.resource;

packages/aws-cdk-lib/aws-s3/test/notification.test.ts

+63-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Template } from '../../assertions';
1+
import { Match, Template } from '../../assertions';
22
import * as iam from '../../aws-iam';
33
import * as cdk from '../../core';
44
import * as s3 from '../lib';
@@ -121,6 +121,68 @@ describe('notification', () => {
121121
});
122122
});
123123

124+
test('custom resource must not depend on bucket policy if it bucket policy does not exists', () => {
125+
const stack = new cdk.Stack();
126+
127+
const bucket = new s3.Bucket(stack, 'MyBucket');
128+
129+
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, {
130+
bind: () => ({
131+
arn: 'ARN',
132+
type: s3.BucketNotificationDestinationType.TOPIC,
133+
}),
134+
});
135+
136+
Template.fromStack(stack).hasResource('Custom::S3BucketNotifications', {
137+
Type: 'Custom::S3BucketNotifications',
138+
DependsOn: Match.absent(),
139+
});
140+
});
141+
142+
test('custom resource must depend on bucket policy to prevent executing too early', () => {
143+
const stack = new cdk.Stack();
144+
145+
const bucket = new s3.Bucket(stack, 'MyBucket', {
146+
enforceSSL: true, // adds bucket policy for test
147+
});
148+
149+
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, {
150+
bind: () => ({
151+
arn: 'ARN',
152+
type: s3.BucketNotificationDestinationType.TOPIC,
153+
}),
154+
});
155+
156+
Template.fromStack(stack).hasResource('Custom::S3BucketNotifications', {
157+
Type: 'Custom::S3BucketNotifications',
158+
DependsOn: ['MyBucketPolicyE7FBAC7B'],
159+
});
160+
});
161+
162+
test('custom resource must depend on bucket policy even if bucket policy is added after notification', () => {
163+
const stack = new cdk.Stack();
164+
165+
const bucket = new s3.Bucket(stack, 'MyBucket');
166+
167+
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, {
168+
bind: () => ({
169+
arn: 'ARN',
170+
type: s3.BucketNotificationDestinationType.TOPIC,
171+
}),
172+
});
173+
174+
bucket.addToResourcePolicy(new iam.PolicyStatement({
175+
resources: [bucket.bucketArn],
176+
actions: ['s3:GetBucketAcl'],
177+
principals: [new iam.AnyPrincipal()],
178+
}));
179+
180+
Template.fromStack(stack).hasResource('Custom::S3BucketNotifications', {
181+
Type: 'Custom::S3BucketNotifications',
182+
DependsOn: ['MyBucketPolicyE7FBAC7B'],
183+
});
184+
});
185+
124186
test('throws with multiple prefix rules in a filter', () => {
125187
const stack = new cdk.Stack();
126188

0 commit comments

Comments
 (0)