Skip to content

Commit

Permalink
feat(events): EventBus policy (aws#23243)
Browse files Browse the repository at this point in the history
Allow users to set the resource policy for an `EventBus`.

Closes aws#23021.

----

### All Submissions:

* [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Construct Runtime Dependencies:

* [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
otaviomacedo authored and Brennan Ho committed Feb 22, 2023
1 parent 6b164d0 commit 2da6412
Show file tree
Hide file tree
Showing 12 changed files with 649 additions and 2 deletions.
74 changes: 73 additions & 1 deletion packages/@aws-cdk/aws-events/lib/event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam';
import { ArnFormat, IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { Archive, BaseArchiveProps } from './archive';
import { CfnEventBus } from './events.generated';
import { CfnEventBus, CfnEventBusPolicy } from './events.generated';

/**
* Interface which all EventBus based classes MUST implement
Expand Down Expand Up @@ -309,6 +309,8 @@ export class EventBus extends EventBusBase {
*/
public readonly eventSourceName?: string;

private policy?: EventBusPolicy;

constructor(scope: Construct, id: string, props?: EventBusProps) {
const { eventBusName, eventSourceName } = EventBus.eventBusProps(
Lazy.string({ produce: () => Names.uniqueId(this) }),
Expand All @@ -332,13 +334,36 @@ export class EventBus extends EventBusBase {
this.eventBusPolicy = eventBus.attrPolicy;
this.eventSourceName = eventBus.eventSourceName;
}

/**
* Adds a statement to the IAM resource policy associated with this event bus.
*/
public addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult {
if (statement.sid == null) {
throw new Error('Event Bus policy statements must have a sid');
}

if (this.policy) {
// The policy can contain only one statement
return { statementAdded: false };
}

this.policy = new EventBusPolicy(this, 'Policy', {
eventBus: this,
statement: statement.toJSON(),
statementId: statement.sid,
});

return { statementAdded: true, policyDependable: this.policy };
}
}

class ImportedEventBus extends EventBusBase {
public readonly eventBusArn: string;
public readonly eventBusName: string;
public readonly eventBusPolicy: string;
public readonly eventSourceName?: string;

constructor(scope: Construct, id: string, attrs: EventBusAttributes) {
const arnParts = Stack.of(scope).splitArn(attrs.eventBusArn, ArnFormat.SLASH_RESOURCE_NAME);
super(scope, id, {
Expand All @@ -352,3 +377,50 @@ class ImportedEventBus extends EventBusBase {
this.eventSourceName = attrs.eventSourceName;
}
}

/**
* Properties to associate Event Buses with a policy
*/
export interface EventBusPolicyProps {
/**
* The event bus to which the policy applies
*/
readonly eventBus: IEventBus;

/**
* An IAM Policy Statement to apply to the Event Bus
*/
readonly statement: iam.PolicyStatement;

/**
* An identifier string for the external account that
* you are granting permissions to.
*/
readonly statementId: string;
}

/**
* The policy for an Event Bus
*
* Policies define the operations that are allowed on this resource.
*
* You almost never need to define this construct directly.
*
* All AWS resources that support resource policies have a method called
* `addToResourcePolicy()`, which will automatically create a new resource
* policy if one doesn't exist yet, otherwise it will add to the existing
* policy.
*
* Prefer to use `addToResourcePolicy()` instead.
*/
export class EventBusPolicy extends Resource {
constructor(scope: Construct, id: string, props: EventBusPolicyProps) {
super(scope, id);

new CfnEventBusPolicy(this, 'Resource', {
statementId: props.statementId!,
statement: props.statement,
eventBusName: props.eventBus.eventBusName,
});
}
}
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@
"props-default-doc:@aws-cdk/aws-events.RuleTargetConfig.runCommandParameters",
"props-default-doc:@aws-cdk/aws-events.RuleTargetConfig.sqsParameters",
"props-default-doc:@aws-cdk/aws-events.RuleTargetConfig.httpParameters",
"from-method:@aws-cdk/aws-events.ApiDestination"
"from-method:@aws-cdk/aws-events.ApiDestination",
"props-physical-name:@aws-cdk/aws-events.EventBusPolicyProps"
]
},
"stability": "stable",
Expand Down
88 changes: 88 additions & 0 deletions packages/@aws-cdk/aws-events/test/event-bus.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Template } from '@aws-cdk/assertions';
import * as iam from '@aws-cdk/aws-iam';
import { Effect } from '@aws-cdk/aws-iam';
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
import { Aws, CfnResource, Stack, Arn, App, PhysicalName, CfnOutput } from '@aws-cdk/core';
import { EventBus } from '../lib';
Expand Down Expand Up @@ -529,4 +530,91 @@ describe('event bus', () => {
Name: 'stack1stack1busca19bdf8ab2e51b62a5a',
});
});

test('can add one event bus policy', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'Stack');
const bus = new EventBus(stack, 'Bus');

// WHEN
bus.addToResourcePolicy(new iam.PolicyStatement({
effect: Effect.ALLOW,
principals: [new iam.AccountPrincipal('111111111111111')],
actions: ['events:PutEvents'],
sid: '123',
resources: [bus.eventBusArn],
}));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBusPolicy', {
StatementId: '123',
EventBusName: {
Ref: 'BusEA82B648',
},
Statement: {
Action: 'events:PutEvents',
Effect: 'Allow',
Principal: {
AWS: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':iam::111111111111111:root',
],
],
},
},
Sid: '123',
Resource: {
'Fn::GetAtt': [
'BusEA82B648',
'Arn',
],
},
},
});
});

test('cannot add more than one event bus policy', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'Stack');
const bus = new EventBus(stack, 'Bus');


const statement = new iam.PolicyStatement({
effect: Effect.ALLOW,
principals: [new iam.ArnPrincipal('arn')],
actions: ['events:PutEvents'],
sid: '123',
resources: [bus.eventBusArn],
});

// WHEN
const add1 = bus.addToResourcePolicy(statement);
const add2 = bus.addToResourcePolicy(statement);

// THEN
expect(add1.statementAdded).toBe(true);
expect(add2.statementAdded).toBe(false);
});

test('Event Bus policy statements must have a sid', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'Stack');
const bus = new EventBus(stack, 'Bus');

// THEN
expect(() => bus.addToResourcePolicy(new iam.PolicyStatement({
effect: Effect.ALLOW,
principals: [new iam.ArnPrincipal('arn')],
actions: ['events:PutEvents'],
}))).toThrow('Event Bus policy statements must have a sid');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "22.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "22.0.0",
"files": {
"c71ed4ea3da796e03fa29834408d0b65d9093011ecfedfbb40bae050834974cc": {
"source": {
"path": "Stack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-us-east-1": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1",
"objectKey": "c71ed4ea3da796e03fa29834408d0b65d9093011ecfedfbb40bae050834974cc.json",
"region": "us-east-1",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"Resources": {
"BusEA82B648": {
"Type": "AWS::Events::EventBus",
"Properties": {
"Name": "StackBusAA0A1E4B"
}
},
"BusPolicyCF00D793": {
"Type": "AWS::Events::EventBusPolicy",
"Properties": {
"StatementId": "123",
"EventBusName": {
"Ref": "BusEA82B648"
},
"Statement": {
"Action": "events:PutEvents",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
"",
[
"arn:aws:iam::",
{
"Ref": "AWS::AccountId"
},
":root"
]
]
}
},
"Resource": {
"Fn::GetAtt": [
"BusEA82B648",
"Arn"
]
},
"Sid": "123"
}
}
}
},
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"22.0.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "22.0.0",
"testCases": {
"IntegTest-BatchDefaultEnvVarsStack/DefaultTest": {
"stacks": [
"Stack"
],
"assertionStack": "IntegTest-BatchDefaultEnvVarsStack/DefaultTest/DeployAssert",
"assertionStackName": "IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2"
}
}
}
Loading

0 comments on commit 2da6412

Please sign in to comment.