Skip to content
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

feat(events): EventBus.grantPutEventsTo method for granular grants #13429

Merged
merged 7 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
```
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-events/lib/event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grantPutEvents is already taken by the static method. Should I drop the static method and rename this ? (That would be a breaking change..)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolving as this a stable module and the contributing guide says no breaking changes on stable modules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yap, that is unfortunate.

Can I ask you to, in this same commit, to @deprecate the current grantPutEvents and add a new one that's called grantAllPutEvents as well?

Then we are set up to rename this to grantPutEvents at some point in the future once we've removed deprecated APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. It's done ! Ready for review

}

/**
Expand Down Expand Up @@ -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],
});
}
}

/**
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down
70 changes: 70 additions & 0 deletions packages/@aws-cdk/aws-events/test/test.event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,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();
Expand Down
24 changes: 17 additions & 7 deletions packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
}
75 changes: 25 additions & 50 deletions packages/@aws-cdk/aws-lambda-destinations/test/destinations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down