diff --git a/packages/aws-cdk-lib/aws-lambda-event-sources/test/s3.test.ts b/packages/aws-cdk-lib/aws-lambda-event-sources/test/s3.test.ts index f5ab8a7fc4bb3..67b63190e315f 100644 --- a/packages/aws-cdk-lib/aws-lambda-event-sources/test/s3.test.ts +++ b/packages/aws-cdk-lib/aws-lambda-event-sources/test/s3.test.ts @@ -228,4 +228,86 @@ describe('S3EventSource', () => { }, }); }); + test('Cross account buckect access', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack'); + const fn = new TestFunction(stack, 'Fn'); + + let accountB = '1234567'; + //WHEN + const foreignBucket = + s3.Bucket.fromBucketAttributes(stack, 'ImportedBucket', { + bucketArn: 'arn:aws:s3:::some-bucket-not-in-this-account', + // The account the bucket really lives in + account: accountB, + }); + + // This will generate the IAM bindings + fn.addEventSource(new sources.S3EventSource(foreignBucket as s3.Bucket, + { events: [s3.EventType.OBJECT_CREATED] })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', { + 'Principal': 's3.amazonaws.com', + 'SourceAccount': '1234567', + 'SourceArn': 'arn:aws:s3:::some-bucket-not-in-this-account', + }); + }); + + test('Test bucket account is referenced intrinsicly', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const bucket = new s3.Bucket(stack, 'B'); + + // WHEN + fn.addEventSource(new sources.S3EventSource(bucket, { + events: [s3.EventType.OBJECT_CREATED, s3.EventType.OBJECT_REMOVED], + filters: [ + { prefix: 'prefix/' }, + { suffix: '.png' }, + ], + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', { + 'Principal': 's3.amazonaws.com', + 'SourceAccount': { + 'Ref': 'AWS::AccountId', + }, + 'SourceArn': { + 'Fn::GetAtt': ['B08E7C7AF', 'Arn'], + }, + }); + }); + + test('Default to stack account if bucket account doesnt exist', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack'); + const fn = new TestFunction(stack, 'Fn'); + + let accountB = ''; + //WHEN + const foreignBucket = + s3.Bucket.fromBucketAttributes(stack, 'ImportedBucket', { + bucketArn: 'arn:aws:s3:::some-bucket-not-in-this-account', + // The account the bucket really lives in + account: accountB, + }); + + // This will generate the IAM bindings + fn.addEventSource(new sources.S3EventSource(foreignBucket as s3.Bucket, + { events: [s3.EventType.OBJECT_CREATED] })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', { + 'Principal': 's3.amazonaws.com', + 'SourceAccount': { + 'Ref': 'AWS::AccountId', + }, + 'SourceArn': 'arn:aws:s3:::some-bucket-not-in-this-account', + }); + }); }); diff --git a/packages/aws-cdk-lib/aws-s3-notifications/lib/lambda.ts b/packages/aws-cdk-lib/aws-s3-notifications/lib/lambda.ts index e6159ff8c22aa..d7e9758eeecb8 100644 --- a/packages/aws-cdk-lib/aws-s3-notifications/lib/lambda.ts +++ b/packages/aws-cdk-lib/aws-s3-notifications/lib/lambda.ts @@ -21,7 +21,7 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { if (bucket.node.tryFindChild(permissionId) === undefined) { this.fn.addPermission(permissionId, { - sourceAccount: Stack.of(bucket).account, + sourceAccount: bucket.account || Stack.of(bucket).account, principal: new iam.ServicePrincipal('s3.amazonaws.com'), sourceArn: bucket.bucketArn, // Placing the permissions node in the same scope as the s3 bucket. diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index a799062afc692..0c9f330163511 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -95,6 +95,11 @@ export interface IBucket extends IResource { */ policy?: BucketPolicy; + /** + * The account bucket belongs to. + */ + readonly account?: string; + /** * Adds a statement to the resource policy for a principal (i.e. * account/role/service) to perform actions on this bucket and/or its @@ -1754,6 +1759,7 @@ export class Bucket extends BucketBase { public readonly bucketWebsiteNewUrlFormat = attrs.bucketWebsiteNewUrlFormat ?? false; public readonly encryptionKey = attrs.encryptionKey; public readonly isWebsite = attrs.isWebsite ?? false; + public readonly account = attrs.account; public policy?: BucketPolicy = undefined; protected autoCreatePolicy = false; protected disallowPublicAccess = false;