From 122a232343699304d8f206d3024fcddfb2a94bc8 Mon Sep 17 00:00:00 2001 From: Alban Esc Date: Tue, 9 Mar 2021 05:11:58 -0800 Subject: [PATCH] feat(events): `EventBus.grantPutEventsTo` method for granular grants (#13429) Right now EventBus has a static method `grantPutEvents()` which grants PutEvents to all EventBridge buses in the account. Adding a `grantPutEventsTo()` method to the IEventBus interface that grants PutEvents to the specific event bus. We are also deprecating `grantPutEvents()` in favor to `grantAllPutEvents()` which has the same behavior. Closes #11228. *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-events/README.md | 14 ++++ packages/@aws-cdk/aws-events/lib/event-bus.ts | 31 ++++++++ .../aws-events/test/test.event-bus.ts | 70 +++++++++++++++++ .../lib/event-bridge.ts | 24 ++++-- .../test/destinations.test.ts | 75 +++++++------------ .../test/integ.destinations.expected.json | 21 +++++- .../test/integ.lambda-chain.expected.json | 42 ++++++++++- 7 files changed, 217 insertions(+), 60 deletions(-) diff --git a/packages/@aws-cdk/aws-events/README.md b/packages/@aws-cdk/aws-events/README.md index 565b0537d091d..83aececc56740 100644 --- a/packages/@aws-cdk/aws-events/README.md +++ b/packages/@aws-cdk/aws-events/README.md @@ -186,3 +186,17 @@ bus.archive('MyArchive', { retention: cdk.Duration.days(365), }); ``` + +## Granting PutEvents to an existing EventBus + +To import an existing EventBus into your CDK application, use `EventBus.fromEventBusArn` or `EventBus.fromEventBusAttributes` +factory method. + +Then, you can use the `grantPutEventsTo` method to grant `event:PutEvents` to the eventBus. + +```ts +const eventBus = EventBus.fromEventBusArn(this, 'ImportedEventBus', 'arn:aws:events:us-east-1:111111111:event-bus/my-event-bus'); + +// now you can just call methods on the eventbus +eventBus.grantPutEventsTo(lambdaFunction); +``` diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index ebbddfe2bc397..2ea9e2300163c 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -47,6 +47,14 @@ export interface IEventBus extends IResource { * @param props Properties of the archive */ archive(id: string, props: BaseArchiveProps): Archive; + + /** + * Grants an IAM Principal to send custom events to the eventBus + * so that they can be matched to rules. + * + * @param grantee The principal (no-op if undefined) + */ + grantPutEventsTo(grantee: iam.IGrantable): iam.Grant; } /** @@ -137,6 +145,14 @@ abstract class EventBusBase extends Resource implements IEventBus { archiveName: props.archiveName, }); } + + public grantPutEventsTo(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['events:PutEvents'], + resourceArns: [this.eventBusArn], + }); + } } /** @@ -177,6 +193,7 @@ export class EventBus extends EventBusBase { * so that they can be matched to rules. * * @param grantee The principal (no-op if undefined) + * @deprecated use grantAllPutEvents instead */ public static grantPutEvents(grantee: iam.IGrantable): iam.Grant { // It's currently not possible to restrict PutEvents to specific resources. @@ -188,6 +205,20 @@ export class EventBus extends EventBusBase { }); } + /** + * Permits an IAM Principal to send custom events to EventBridge + * so that they can be matched to rules. + * + * @param grantee The principal (no-op if undefined) + */ + public static grantAllPutEvents(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['events:PutEvents'], + resourceArns: ['*'], + }); + } + private static eventBusProps(defaultEventBusName: string, props?: EventBusProps) { if (props) { const { eventBusName, eventSourceName } = props; diff --git a/packages/@aws-cdk/aws-events/test/test.event-bus.ts b/packages/@aws-cdk/aws-events/test/test.event-bus.ts index 2156b39e06e8d..2babb3f9b659e 100644 --- a/packages/@aws-cdk/aws-events/test/test.event-bus.ts +++ b/packages/@aws-cdk/aws-events/test/test.event-bus.ts @@ -306,6 +306,76 @@ export = { test.done(); }, + + 'can grant PutEvents using grantAllPutEvents'(test: Test) { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + // WHEN + EventBus.grantAllPutEvents(role); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'events:PutEvents', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + Roles: [ + { + Ref: 'Role1ABCC5F0', + }, + ], + })); + + test.done(); + }, + 'can grant PutEvents to a specific event bus'(test: Test) { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + const eventBus = new EventBus(stack, 'EventBus'); + + // WHEN + eventBus.grantPutEventsTo(role); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'events:PutEvents', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'EventBus7B8748AA', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + Roles: [ + { + Ref: 'Role1ABCC5F0', + }, + ], + })); + + test.done(); + }, 'can archive events'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts b/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts index f61d8409da7bd..9cdcc5c86a83b 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts +++ b/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts @@ -22,15 +22,25 @@ export class EventBridgeDestination implements lambda.IDestination { * Returns a destination configuration */ public bind(_scope: Construct, fn: lambda.IFunction, _options?: lambda.DestinationOptions): lambda.DestinationConfig { - // deduplicated automatically - events.EventBus.grantPutEvents(fn); // Cannot restrict to a specific resource + if (this.eventBus) { + this.eventBus.grantPutEventsTo(fn); + + return { + destination: this.eventBus.eventBusArn, + }; + } + + const existingDefaultEventBus = _scope.node.tryFindChild('DefaultEventBus'); + let eventBus = (existingDefaultEventBus as events.EventBus) || events.EventBus.fromEventBusArn(_scope, 'DefaultEventBus', Stack.of(fn).formatArn({ + service: 'events', + resource: 'event-bus', + resourceName: 'default', + })); + + eventBus.grantPutEventsTo(fn); return { - destination: this.eventBus && this.eventBus.eventBusArn || Stack.of(fn).formatArn({ - service: 'events', - resource: 'event-bus', - resourceName: 'default', - }), + destination: eventBus.eventBusArn, }; } } diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/destinations.test.ts b/packages/@aws-cdk/aws-lambda-destinations/test/destinations.test.ts index 0f8f7d4c85254..70dda9ef43893 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/destinations.test.ts +++ b/packages/@aws-cdk/aws-lambda-destinations/test/destinations.test.ts @@ -47,56 +47,12 @@ test('event bus as destination', () => { { Action: 'events:PutEvents', Effect: 'Allow', - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - }); -}); - -test('event bus as destination defaults to default event bus', () => { - // WHEN - new lambda.Function(stack, 'Function', { - ...lambdaProps, - onSuccess: new destinations.EventBridgeDestination(), - }); - - // THEN - expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { - DestinationConfig: { - OnSuccess: { - Destination: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':events:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':event-bus/default', + Resource: { + 'Fn::GetAtt': [ + 'EventBus7B8748AA', + 'Arn', ], - ], - }, - }, - }, - }); - - expect(stack).toHaveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'events:PutEvents', - Effect: 'Allow', - Resource: '*', + }, }, ], Version: '2012-10-17', @@ -215,7 +171,26 @@ test('lambda payload as destination', () => { { Action: 'events:PutEvents', Effect: 'Allow', - Resource: '*', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':events:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':event-bus/default', + ], + ], + }, }, ], Version: '2012-10-17', diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/integ.destinations.expected.json b/packages/@aws-cdk/aws-lambda-destinations/test/integ.destinations.expected.json index 510fe8cef21a8..009327c46da7e 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/integ.destinations.expected.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/integ.destinations.expected.json @@ -219,7 +219,26 @@ { "Action": "events:PutEvents", "Effect": "Allow", - "Resource": "*" + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":event-bus/default" + ] + ] + } }, { "Action": "lambda:InvokeFunction", diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json b/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json index 5fc64df8417f3..5fc2d65b80387 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json @@ -39,7 +39,26 @@ { "Action": "events:PutEvents", "Effect": "Allow", - "Resource": "*" + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":event-bus/default" + ] + ] + } } ], "Version": "2012-10-17" @@ -289,7 +308,26 @@ { "Action": "events:PutEvents", "Effect": "Allow", - "Resource": "*" + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":event-bus/default" + ] + ] + } } ], "Version": "2012-10-17"