Skip to content

Commit 15ced88

Browse files
authored
fix(cloudtrail): Trail fails during resource creation due to invalid template properties when management events are 'None' (#23569)
#### Overview Currently CDK produces invalid CloudFormation that fails validation by the CloudTrail API upon deployment when setting the `managementEvents` parameter to `ReadWriteType.NONE`. Although this is a contribution to a stable module, I consider this change to not have breaking changes as the original implementation generates CloudFormation that would result in stack deployment failure so is currently broken. #### Behaviour changes *Setting `managementEvents` to `ReadWriteType.NONE`* **Old behaviour:** Successfully synthesises but produces CloudFormation that fails to deploy. **New behaviour:** Fails synthesis with a validation error if no additional event selectors are added to the trail, as the default behaviour of CloudTrail is to enable management events if no event selectors are provided. Sets `includeManagementEvents` to `false` by default when new event selectors are added unless overridden explicitly by the user. #### Other options considered The previous PR had a suggestion to just always set `includeManagementEvents` to `false` when adding additional event selectors rather than only setting it to `false` when the Trail was created with `ReadWriteType.NONE`. However, this would break backwards compatibility for some scenarios (such as where users don't set `managementEvents` when creating the trail and later add an event selector, as well as a bunch of other esoteric edge cases). #### Related Previous PR that was closed for staleness (#16387). Closes #15488 ---- ### All Submissions: * [X] 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 * [X] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [X] 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 b4a6120 commit 15ced88

20 files changed

+1639
-21
lines changed

packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts

+24-15
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ export class Trail extends Resource {
235235
public readonly logGroup?: logs.ILogGroup;
236236

237237
private s3bucket: s3.IBucket;
238+
private managementEvents: ReadWriteType | undefined;
238239
private eventSelectors: EventSelector[] = [];
239240
private topic: sns.ITopic | undefined;
240241
private insightTypeValues: InsightSelector[] | undefined;
@@ -289,20 +290,14 @@ export class Trail extends Resource {
289290
}));
290291
}
291292

292-
if (props.managementEvents) {
293-
let managementEvent;
294-
if (props.managementEvents === ReadWriteType.NONE) {
295-
managementEvent = {
296-
includeManagementEvents: false,
297-
};
298-
} else {
299-
managementEvent = {
300-
includeManagementEvents: true,
301-
readWriteType: props.managementEvents,
302-
};
303-
}
304-
this.eventSelectors.push(managementEvent);
293+
this.managementEvents = props.managementEvents;
294+
if (this.managementEvents && this.managementEvents !== ReadWriteType.NONE) {
295+
this.eventSelectors.push({
296+
includeManagementEvents: true,
297+
readWriteType: props.managementEvents,
298+
});
305299
}
300+
this.node.addValidation({ validate: () => this.validateEventSelectors() });
306301

307302
if (props.kmsKey && props.encryptionKey) {
308303
throw new Error('Both kmsKey and encryptionKey must not be specified. Use only encryptionKey');
@@ -373,12 +368,17 @@ export class Trail extends Resource {
373368
throw new Error('A maximum of 5 event selectors are supported per trail.');
374369
}
375370

371+
let includeAllManagementEvents;
372+
if (this.managementEvents === ReadWriteType.NONE) {
373+
includeAllManagementEvents = false;
374+
}
375+
376376
this.eventSelectors.push({
377377
dataResources: [{
378378
type: dataResourceType,
379379
values: dataResourceValues,
380380
}],
381-
includeManagementEvents: options.includeManagementEvents,
381+
includeManagementEvents: options.includeManagementEvents ?? includeAllManagementEvents,
382382
excludeManagementEventSources: options.excludeManagementEventSources,
383383
readWriteType: options.readWriteType,
384384
});
@@ -403,7 +403,7 @@ export class Trail extends Resource {
403403
}
404404

405405
/**
406-
* Log all Lamda data events for all lambda functions the account.
406+
* Log all Lambda data events for all lambda functions the account.
407407
* @see https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html
408408
* @default false
409409
*/
@@ -451,6 +451,15 @@ export class Trail extends Resource {
451451
public onCloudTrailEvent(id: string, options: events.OnEventOptions = {}): events.Rule {
452452
return Trail.onEvent(this, id, options);
453453
}
454+
455+
private validateEventSelectors(): string[] {
456+
const errors: string[] = [];
457+
// Ensure that there is at least one event selector when management events are set to None
458+
if (this.managementEvents === ReadWriteType.NONE && this.eventSelectors.length === 0) {
459+
errors.push('At least one event selector must be added when management event recording is set to None');
460+
}
461+
return errors;
462+
}
454463
}
455464

456465
/**

packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts

+64-6
Original file line numberDiff line numberDiff line change
@@ -625,19 +625,77 @@ describe('cloudtrail', () => {
625625
});
626626
});
627627

628-
test('managementEvents set to None correctly turns off management events', () => {
628+
test('not provided and managementEvents set to None throws missing event selectors error', () => {
629629
const stack = getTestStack();
630630

631631
new Trail(stack, 'MyAmazingCloudTrail', {
632632
managementEvents: ReadWriteType.NONE,
633633
});
634634

635+
expect(() => {
636+
Template.fromStack(stack);
637+
}).toThrowError(/At least one event selector must be added when management event recording is set to None/);
638+
});
639+
640+
test('defaults to not include management events when managementEvents set to None', () => {
641+
const stack = getTestStack();
642+
643+
const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail', {
644+
managementEvents: ReadWriteType.NONE,
645+
});
646+
647+
const bucket = new s3.Bucket(stack, 'testBucket', { bucketName: 'test-bucket' });
648+
cloudTrail.addS3EventSelector([{ bucket }]);
649+
635650
Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', {
636-
EventSelectors: [
637-
{
638-
IncludeManagementEvents: false,
639-
},
640-
],
651+
EventSelectors: [{
652+
DataResources: [{
653+
Type: 'AWS::S3::Object',
654+
Values: [{
655+
'Fn::Join': [
656+
'',
657+
[
658+
{ 'Fn::GetAtt': ['testBucketDF4D7D1A', 'Arn'] },
659+
'/',
660+
],
661+
],
662+
}],
663+
}],
664+
IncludeManagementEvents: false,
665+
}],
666+
});
667+
});
668+
669+
test('includeManagementEvents can be overridden when managementEvents set to None', () => {
670+
const stack = getTestStack();
671+
672+
const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail', {
673+
managementEvents: ReadWriteType.NONE,
674+
});
675+
676+
const bucket = new s3.Bucket(stack, 'testBucket', { bucketName: 'test-bucket' });
677+
cloudTrail.addS3EventSelector([{ bucket }], {
678+
includeManagementEvents: true,
679+
readWriteType: ReadWriteType.WRITE_ONLY,
680+
});
681+
682+
Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', {
683+
EventSelectors: [{
684+
DataResources: [{
685+
Type: 'AWS::S3::Object',
686+
Values: [{
687+
'Fn::Join': [
688+
'',
689+
[
690+
{ 'Fn::GetAtt': ['testBucketDF4D7D1A', 'Arn'] },
691+
'/',
692+
],
693+
],
694+
}],
695+
}],
696+
IncludeManagementEvents: true,
697+
ReadWriteType: 'WriteOnly',
698+
}],
641699
});
642700
});
643701

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": "CloudTrailDataEventsOnlyTestDefaultTestDeployAssertA7E52868.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 @@
1+
{"version":"22.0.0"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "22.0.0",
3+
"files": {
4+
"1e43b2272a716f06e79a67fe7810bd64d2c4f198ec606c15bac1ce856e05dbbc": {
5+
"source": {
6+
"path": "integ-cloudtrail-data-events.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "1e43b2272a716f06e79a67fe7810bd64d2c4f198ec606c15bac1ce856e05dbbc.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+
}

0 commit comments

Comments
 (0)