Skip to content

Commit 7a3de0a

Browse files
authored
feat(events): EventBus policy (#23243)
Allow users to set the resource policy for an `EventBus`. Closes #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*
1 parent bef86f6 commit 7a3de0a

12 files changed

+649
-2
lines changed

Diff for: packages/@aws-cdk/aws-events/lib/event-bus.ts

+73-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam';
22
import { ArnFormat, IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core';
33
import { Construct } from 'constructs';
44
import { Archive, BaseArchiveProps } from './archive';
5-
import { CfnEventBus } from './events.generated';
5+
import { CfnEventBus, CfnEventBusPolicy } from './events.generated';
66

77
/**
88
* Interface which all EventBus based classes MUST implement
@@ -309,6 +309,8 @@ export class EventBus extends EventBusBase {
309309
*/
310310
public readonly eventSourceName?: string;
311311

312+
private policy?: EventBusPolicy;
313+
312314
constructor(scope: Construct, id: string, props?: EventBusProps) {
313315
const { eventBusName, eventSourceName } = EventBus.eventBusProps(
314316
Lazy.string({ produce: () => Names.uniqueId(this) }),
@@ -332,13 +334,36 @@ export class EventBus extends EventBusBase {
332334
this.eventBusPolicy = eventBus.attrPolicy;
333335
this.eventSourceName = eventBus.eventSourceName;
334336
}
337+
338+
/**
339+
* Adds a statement to the IAM resource policy associated with this event bus.
340+
*/
341+
public addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult {
342+
if (statement.sid == null) {
343+
throw new Error('Event Bus policy statements must have a sid');
344+
}
345+
346+
if (this.policy) {
347+
// The policy can contain only one statement
348+
return { statementAdded: false };
349+
}
350+
351+
this.policy = new EventBusPolicy(this, 'Policy', {
352+
eventBus: this,
353+
statement: statement.toJSON(),
354+
statementId: statement.sid,
355+
});
356+
357+
return { statementAdded: true, policyDependable: this.policy };
358+
}
335359
}
336360

337361
class ImportedEventBus extends EventBusBase {
338362
public readonly eventBusArn: string;
339363
public readonly eventBusName: string;
340364
public readonly eventBusPolicy: string;
341365
public readonly eventSourceName?: string;
366+
342367
constructor(scope: Construct, id: string, attrs: EventBusAttributes) {
343368
const arnParts = Stack.of(scope).splitArn(attrs.eventBusArn, ArnFormat.SLASH_RESOURCE_NAME);
344369
super(scope, id, {
@@ -352,3 +377,50 @@ class ImportedEventBus extends EventBusBase {
352377
this.eventSourceName = attrs.eventSourceName;
353378
}
354379
}
380+
381+
/**
382+
* Properties to associate Event Buses with a policy
383+
*/
384+
export interface EventBusPolicyProps {
385+
/**
386+
* The event bus to which the policy applies
387+
*/
388+
readonly eventBus: IEventBus;
389+
390+
/**
391+
* An IAM Policy Statement to apply to the Event Bus
392+
*/
393+
readonly statement: iam.PolicyStatement;
394+
395+
/**
396+
* An identifier string for the external account that
397+
* you are granting permissions to.
398+
*/
399+
readonly statementId: string;
400+
}
401+
402+
/**
403+
* The policy for an Event Bus
404+
*
405+
* Policies define the operations that are allowed on this resource.
406+
*
407+
* You almost never need to define this construct directly.
408+
*
409+
* All AWS resources that support resource policies have a method called
410+
* `addToResourcePolicy()`, which will automatically create a new resource
411+
* policy if one doesn't exist yet, otherwise it will add to the existing
412+
* policy.
413+
*
414+
* Prefer to use `addToResourcePolicy()` instead.
415+
*/
416+
export class EventBusPolicy extends Resource {
417+
constructor(scope: Construct, id: string, props: EventBusPolicyProps) {
418+
super(scope, id);
419+
420+
new CfnEventBusPolicy(this, 'Resource', {
421+
statementId: props.statementId!,
422+
statement: props.statement,
423+
eventBusName: props.eventBus.eventBusName,
424+
});
425+
}
426+
}

Diff for: packages/@aws-cdk/aws-events/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@
115115
"props-default-doc:@aws-cdk/aws-events.RuleTargetConfig.runCommandParameters",
116116
"props-default-doc:@aws-cdk/aws-events.RuleTargetConfig.sqsParameters",
117117
"props-default-doc:@aws-cdk/aws-events.RuleTargetConfig.httpParameters",
118-
"from-method:@aws-cdk/aws-events.ApiDestination"
118+
"from-method:@aws-cdk/aws-events.ApiDestination",
119+
"props-physical-name:@aws-cdk/aws-events.EventBusPolicyProps"
119120
]
120121
},
121122
"stability": "stable",

Diff for: packages/@aws-cdk/aws-events/test/event-bus.test.ts

+88
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Template } from '@aws-cdk/assertions';
22
import * as iam from '@aws-cdk/aws-iam';
3+
import { Effect } from '@aws-cdk/aws-iam';
34
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
45
import { Aws, CfnResource, Stack, Arn, App, PhysicalName, CfnOutput } from '@aws-cdk/core';
56
import { EventBus } from '../lib';
@@ -529,4 +530,91 @@ describe('event bus', () => {
529530
Name: 'stack1stack1busca19bdf8ab2e51b62a5a',
530531
});
531532
});
533+
534+
test('can add one event bus policy', () => {
535+
// GIVEN
536+
const app = new App();
537+
const stack = new Stack(app, 'Stack');
538+
const bus = new EventBus(stack, 'Bus');
539+
540+
// WHEN
541+
bus.addToResourcePolicy(new iam.PolicyStatement({
542+
effect: Effect.ALLOW,
543+
principals: [new iam.AccountPrincipal('111111111111111')],
544+
actions: ['events:PutEvents'],
545+
sid: '123',
546+
resources: [bus.eventBusArn],
547+
}));
548+
549+
// THEN
550+
Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBusPolicy', {
551+
StatementId: '123',
552+
EventBusName: {
553+
Ref: 'BusEA82B648',
554+
},
555+
Statement: {
556+
Action: 'events:PutEvents',
557+
Effect: 'Allow',
558+
Principal: {
559+
AWS: {
560+
'Fn::Join': [
561+
'',
562+
[
563+
'arn:',
564+
{
565+
Ref: 'AWS::Partition',
566+
},
567+
':iam::111111111111111:root',
568+
],
569+
],
570+
},
571+
},
572+
Sid: '123',
573+
Resource: {
574+
'Fn::GetAtt': [
575+
'BusEA82B648',
576+
'Arn',
577+
],
578+
},
579+
},
580+
});
581+
});
582+
583+
test('cannot add more than one event bus policy', () => {
584+
// GIVEN
585+
const app = new App();
586+
const stack = new Stack(app, 'Stack');
587+
const bus = new EventBus(stack, 'Bus');
588+
589+
590+
const statement = new iam.PolicyStatement({
591+
effect: Effect.ALLOW,
592+
principals: [new iam.ArnPrincipal('arn')],
593+
actions: ['events:PutEvents'],
594+
sid: '123',
595+
resources: [bus.eventBusArn],
596+
});
597+
598+
// WHEN
599+
const add1 = bus.addToResourcePolicy(statement);
600+
const add2 = bus.addToResourcePolicy(statement);
601+
602+
// THEN
603+
expect(add1.statementAdded).toBe(true);
604+
expect(add2.statementAdded).toBe(false);
605+
});
606+
607+
test('Event Bus policy statements must have a sid', () => {
608+
// GIVEN
609+
const app = new App();
610+
const stack = new Stack(app, 'Stack');
611+
const bus = new EventBus(stack, 'Bus');
612+
613+
// THEN
614+
expect(() => bus.addToResourcePolicy(new iam.PolicyStatement({
615+
effect: Effect.ALLOW,
616+
principals: [new iam.ArnPrincipal('arn')],
617+
actions: ['events:PutEvents'],
618+
}))).toThrow('Event Bus policy statements must have a sid');
619+
});
532620
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "22.0.0",
3+
"files": {
4+
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
5+
"source": {
6+
"path": "IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"Parameters": {
3+
"BootstrapVersion": {
4+
"Type": "AWS::SSM::Parameter::Value<String>",
5+
"Default": "/cdk-bootstrap/hnb659fds/version",
6+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
7+
}
8+
},
9+
"Rules": {
10+
"CheckBootstrapVersion": {
11+
"Assertions": [
12+
{
13+
"Assert": {
14+
"Fn::Not": [
15+
{
16+
"Fn::Contains": [
17+
[
18+
"1",
19+
"2",
20+
"3",
21+
"4",
22+
"5"
23+
],
24+
{
25+
"Ref": "BootstrapVersion"
26+
}
27+
]
28+
}
29+
]
30+
},
31+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
32+
}
33+
]
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"version": "22.0.0",
3+
"files": {
4+
"c71ed4ea3da796e03fa29834408d0b65d9093011ecfedfbb40bae050834974cc": {
5+
"source": {
6+
"path": "Stack.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-us-east-1": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1",
12+
"objectKey": "c71ed4ea3da796e03fa29834408d0b65d9093011ecfedfbb40bae050834974cc.json",
13+
"region": "us-east-1",
14+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1"
15+
}
16+
}
17+
}
18+
},
19+
"dockerImages": {}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"Resources": {
3+
"BusEA82B648": {
4+
"Type": "AWS::Events::EventBus",
5+
"Properties": {
6+
"Name": "StackBusAA0A1E4B"
7+
}
8+
},
9+
"BusPolicyCF00D793": {
10+
"Type": "AWS::Events::EventBusPolicy",
11+
"Properties": {
12+
"StatementId": "123",
13+
"EventBusName": {
14+
"Ref": "BusEA82B648"
15+
},
16+
"Statement": {
17+
"Action": "events:PutEvents",
18+
"Effect": "Allow",
19+
"Principal": {
20+
"AWS": {
21+
"Fn::Join": [
22+
"",
23+
[
24+
"arn:aws:iam::",
25+
{
26+
"Ref": "AWS::AccountId"
27+
},
28+
":root"
29+
]
30+
]
31+
}
32+
},
33+
"Resource": {
34+
"Fn::GetAtt": [
35+
"BusEA82B648",
36+
"Arn"
37+
]
38+
},
39+
"Sid": "123"
40+
}
41+
}
42+
}
43+
},
44+
"Parameters": {
45+
"BootstrapVersion": {
46+
"Type": "AWS::SSM::Parameter::Value<String>",
47+
"Default": "/cdk-bootstrap/hnb659fds/version",
48+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
49+
}
50+
},
51+
"Rules": {
52+
"CheckBootstrapVersion": {
53+
"Assertions": [
54+
{
55+
"Assert": {
56+
"Fn::Not": [
57+
{
58+
"Fn::Contains": [
59+
[
60+
"1",
61+
"2",
62+
"3",
63+
"4",
64+
"5"
65+
],
66+
{
67+
"Ref": "BootstrapVersion"
68+
}
69+
]
70+
}
71+
]
72+
},
73+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
74+
}
75+
]
76+
}
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"version":"22.0.0"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "22.0.0",
3+
"testCases": {
4+
"IntegTest-BatchDefaultEnvVarsStack/DefaultTest": {
5+
"stacks": [
6+
"Stack"
7+
],
8+
"assertionStack": "IntegTest-BatchDefaultEnvVarsStack/DefaultTest/DeployAssert",
9+
"assertionStackName": "IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2"
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)