-
Couldn't load subscription status.
- Fork 4.3k
fix(s3): scope BucketNotificationsHandler IAM permissions to specific bucket ARNs #35334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(s3): scope BucketNotificationsHandler IAM permissions to specific bucket ARNs #35334
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using CDK aspects for this is overkill, can't we just grant the resource directly with the s3 bucket? preferably using grant methods instead of inline policies?
|
We actually have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left comment
|
|
||
| /** | ||
| * Grant permissions for managing bucket notifications. | ||
| * | ||
| * Grants scoped IAM permissions to the specific bucket ARN instead of using wildcard permissions. | ||
| * This implements the principle of least privilege by limiting the handler's access to only | ||
| * the buckets it needs to manage. | ||
| * | ||
| * @param bucketArn The ARN of the bucket to grant permissions for | ||
| * @param isUnmanaged Whether this is an unmanaged (imported) bucket that needs GetBucketNotification permissions | ||
| */ | ||
| public grantBucketNotifications(bucketArn: string, isUnmanaged: boolean = false) { | ||
| // All buckets need PutBucketNotification to set/update notification configurations | ||
| this.role.addToPrincipalPolicy(new iam.PolicyStatement({ | ||
| actions: ['s3:PutBucketNotification'], | ||
| resources: [bucketArn], | ||
| })); | ||
|
|
||
| // Unmanaged (imported) buckets need GetBucketNotification to read existing configurations | ||
| // before merging with new notifications. Managed buckets don't need this since CDK | ||
| // controls their complete notification state from creation. | ||
| if (isUnmanaged) { | ||
| this.role.addToPrincipalPolicy(new iam.PolicyStatement({ | ||
| actions: ['s3:GetBucketNotification'], | ||
| resources: [bucketArn], | ||
| })); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grantBucketNotifications() method should be private within the notifications-resource.ts as it is not needed to have this method as public. It is also not used inside resource handler. Let's move method and make it private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ozelalisen
The grantBucketNotifications() method needs to remain public as it's actively used by the BucketNotifications class and follows established CDK design patterns. Making it private would break existing functionality and violate CDK conventions.
Current Usage Evidence
The method is called in packages/aws-cdk-lib/aws-s3/lib/notifications-resource/notifications-resource.ts at line 142:
private createResourceOnce() {
if (!this.resource) {
const handler = NotificationsResourceHandler.singleton(this, {
role: this.handlerRole,
});
let managed = this.bucket instanceof Bucket;
// Feature flag handling for imported buckets
if (cdk.FeatureFlags.of(this).isEnabled(cxapi.S3_KEEP_NOTIFICATION_IN_IMPORTED_BUCKET)) {
managed = false;
}
// ACTIVE USAGE - External class calling public method
handler.grantBucketNotifications(this.bucket.bucketArn, !managed);
this.resource = new cdk.CfnResource(this, 'Resource', {
type: 'Custom::S3BucketNotifications',
// ... properties
});
}
return this.resource;
}Design Pattern Analysis
Current Approach (Public Instance Method)
// Object-oriented, follows CDK patterns
handler.grantBucketNotifications(bucketArn, isUnmanaged);Advantages:
- Follows established CDK patterns (
bucket.grantRead(),table.grantWriteData(), etc.) - Encapsulates permission management within the handler construct
- Object-oriented design - handler manages its own IAM state
- Consistent with CDK ecosystem conventions
- Future-proof for adding permission tracking or validation logic
- Clear ownership - the handler is responsible for its own permissions
Alternative Approach (Private/Static Function)
// Procedural approach, would require refactoring
NotificationsResourceHandler.grantBucketNotifications(handler, bucketArn, isUnmanaged);
// or
grantBucketNotifications(handler, bucketArn, isUnmanaged);Disadvantages:
- Not following our established CDK design conventions
- Exposes internal role management to external callers
- Inconsistent with other
grant*methods across the CDK ecosystem - Harder to extend with additional logic or state management
References
We currently have some public grant* method pattern is used throughout CDK:
// S3 Bucket
bucket.grantRead(role);
bucket.grantWrite(role);
// DynamoDB Table
table.grantReadData(role);
table.grantWriteData(role);
// Lambda Function
func.grantInvoke(role);
// Our Handler (consistent pattern)
handler.grantBucketNotifications(bucketArn, isUnmanaged);wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand, how it would brake customer experience. It was added with this PR, so it did not exist before, it is a new method. Regarding best practices for grant pattern, this is not following the Design Guidelines, grant pattern should follow:
- Name should have a “grant” prefix
- Returns an iam.Grant object
- First argument must be grantee: iam.IGrantable
I am fine to keep it in handler, but I think isUnmanaged property in method is not very clear from its name and best practice for grant should be followed. I would propose to rename it to something more clear, as this is exposed publicly. It is very clear for someone who knows the class, but as an exposed public api, it is not easy to understand from this name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ozelalisen Thank you for your patience and great review. After second consideration, I made it a private addHandlerPermissions() method and moved to the notifications-resource.ts. Let me know if you have any other concerns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
@Mergifyio update |
✅ Branch has been successfully updated |
|
@Mergifyio requeue |
✅ The queue state of this pull request has been cleaned. It can be re-embarked automatically |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dismissing review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dismissing my review.
|
Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
|
Comments on closed issues and PRs are hard for our team to see. |
Issue # (if applicable)
Closes #35331 #5925
Reason for this change
The S3 BucketNotificationsHandler Lambda function created by CDK for S3 bucket notifications receives overly broad IAM permissions with
"Resource": "*"instead of being scoped to specific bucket ARNs. This violates the principle of least privilege and creates a security risk where the handler could potentially modify notifications for any S3 bucket in the account, preventing security teams from approving deployments.Description of changes
Implemented scoped IAM permissions for the S3
BucketNotificationsHandlerby leveraging the existinggrantBucketNotifications()method and the@aws-cdk/aws-iam:minimizePoliciesfeature flag:@aws-cdk/aws-iam:minimizePoliciesfeature flag automatically consolidates IAM policies with specific bucket ARNsgrantBucketNotifications()method which already provides scoped permissions to specific bucket ARNsBefore: IAM policy contained
"Resource": "*"(wildcard permissions)After: IAM policy contains
"Resource": [{"Fn::GetAtt": ["BucketName", "Arn"]}, ...](scoped permissions)Describe any new or updated permissions being added
Security improvement: This change reduces permissions rather than adding new ones. The handler now receives more restrictive IAM permissions scoped only to the specific S3 buckets it needs to manage, implementing the principle of least privilege. No new permissions are required.
Description of how you validated changes
Potential Concerns
Simplified Implementation: The current approach leverages existing CDK mechanisms (
grantBucketNotifications()method and@aws-cdk/aws-iam:minimizePoliciesfeature flag) rather than implementing custom Aspect logic. This is actually a strength as it reduces complexity and maintenance burden while achieving the same security outcome.Feature Flag Dependency: The solution relies on the
@aws-cdk/aws-iam:minimizePoliciesfeature flag for policy consolidation. This flag is already widely adopted and is part of CDK's recommended feature flags, making this a low-risk dependency.Potential for Regressions: The PR fixes a security vulnerability that has been present for a while. The existing comprehensive test suite provides good coverage, and the integration tests serve as the best defense against future regressions.
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license