Skip to content

Commit e42ed53

Browse files
Niranjan Jayakarkarupanerura
Niranjan Jayakar
authored andcommitted
feat(cloudtrail): user specified log group (aws#8079)
Allow for users to set their own log group that CloudTrail must send events to. Expose a log group instance property that returns the user specified or auto-created log group. closes aws#6162 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent b871fde commit e42ed53

File tree

3 files changed

+68
-12
lines changed

3 files changed

+68
-12
lines changed

packages/@aws-cdk/aws-cloudtrail/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ const trail = new cloudtrail.Trail(this, 'CloudTrail', {
5151
```
5252

5353
This creates the same setup as above - but also logs events to a created CloudWatch Log stream.
54-
By default, the created log group has a retention period of 365 Days, but this is also configurable.
54+
By default, the created log group has a retention period of 365 Days, but this is also configurable
55+
via the `cloudWatchLogsRetention` property. If you would like to specify the log group explicitly,
56+
use the `cloudwatchLogGroup` property.
5557

5658
For using CloudTrail event selector to log specific S3 events,
5759
you can use the `CloudTrailProps` configuration object.

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

+25-9
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,19 @@ export interface TrailProps {
6363
readonly sendToCloudWatchLogs?: boolean;
6464

6565
/**
66-
* How long to retain logs in CloudWatchLogs. Ignored if sendToCloudWatchLogs is false
66+
* How long to retain logs in CloudWatchLogs.
67+
* Ignored if sendToCloudWatchLogs is false or if cloudWatchLogGroup is set.
6768
*
68-
* @default logs.RetentionDays.OneYear
69+
* @default logs.RetentionDays.ONE_YEAR
6970
*/
7071
readonly cloudWatchLogsRetention?: logs.RetentionDays;
7172

73+
/**
74+
* Log Group to which CloudTrail to push logs to. Ignored if sendToCloudWatchLogs is set to false.
75+
* @default - a new log group is created and used.
76+
*/
77+
readonly cloudWatchLogGroup?: logs.ILogGroup;
78+
7279
/** The AWS Key Management Service (AWS KMS) key ID that you want to use to encrypt CloudTrail logs.
7380
*
7481
* @default - No encryption.
@@ -171,6 +178,12 @@ export class Trail extends Resource {
171178
*/
172179
public readonly trailSnsTopicArn: string;
173180

181+
/**
182+
* The CloudWatch log group to which CloudTrail events are sent.
183+
* `undefined` if `sendToCloudWatchLogs` property is false.
184+
*/
185+
public readonly logGroup?: logs.ILogGroup;
186+
174187
private s3bucket: s3.IBucket;
175188
private eventSelectors: EventSelector[] = [];
176189

@@ -200,19 +213,22 @@ export class Trail extends Resource {
200213
},
201214
}));
202215

203-
let logGroup: logs.CfnLogGroup | undefined;
204216
let logsRole: iam.IRole | undefined;
205217

206218
if (props.sendToCloudWatchLogs) {
207-
logGroup = new logs.CfnLogGroup(this, 'LogGroup', {
208-
retentionInDays: props.cloudWatchLogsRetention || logs.RetentionDays.ONE_YEAR,
209-
});
219+
if (props.cloudWatchLogGroup) {
220+
this.logGroup = props.cloudWatchLogGroup;
221+
} else {
222+
this.logGroup = new logs.LogGroup(this, 'LogGroup', {
223+
retention: props.cloudWatchLogsRetention ?? logs.RetentionDays.ONE_YEAR,
224+
});
225+
}
210226

211227
logsRole = new iam.Role(this, 'LogsRole', { assumedBy: cloudTrailPrincipal });
212228

213229
logsRole.addToPolicy(new iam.PolicyStatement({
214230
actions: ['logs:PutLogEvents', 'logs:CreateLogStream'],
215-
resources: [logGroup.attrArn],
231+
resources: [this.logGroup.logGroupArn],
216232
}));
217233
}
218234

@@ -234,8 +250,8 @@ export class Trail extends Resource {
234250
kmsKeyId: props.kmsKey && props.kmsKey.keyArn,
235251
s3BucketName: this.s3bucket.bucketName,
236252
s3KeyPrefix: props.s3KeyPrefix,
237-
cloudWatchLogsLogGroupArn: logGroup && logGroup.attrArn,
238-
cloudWatchLogsRoleArn: logsRole && logsRole.roleArn,
253+
cloudWatchLogsLogGroupArn: this.logGroup?.logGroupArn,
254+
cloudWatchLogsRoleArn: logsRole?.roleArn,
239255
snsTopicName: props.snsTopic,
240256
eventSelectors: this.eventSelectors,
241257
});

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

+40-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SynthUtils } from '@aws-cdk/assert';
22
import '@aws-cdk/assert/jest';
33
import * as iam from '@aws-cdk/aws-iam';
44
import * as lambda from '@aws-cdk/aws-lambda';
5-
import { RetentionDays } from '@aws-cdk/aws-logs';
5+
import { LogGroup, RetentionDays } from '@aws-cdk/aws-logs';
66
import * as s3 from '@aws-cdk/aws-s3';
77
import { Stack } from '@aws-cdk/core';
88
import { ReadWriteType, Trail } from '../lib';
@@ -176,7 +176,7 @@ describe('cloudtrail', () => {
176176
Effect: 'Allow',
177177
Action: ['logs:PutLogEvents', 'logs:CreateLogStream'],
178178
Resource: {
179-
'Fn::GetAtt': ['MyAmazingCloudTrailLogGroupAAD65144', 'Arn'],
179+
'Fn::GetAtt': ['MyAmazingCloudTrailLogGroup2BE67F87', 'Arn'],
180180
},
181181
}],
182182
},
@@ -205,6 +205,44 @@ describe('cloudtrail', () => {
205205
const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D;
206206
expect(trail.DependsOn).toEqual([logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']);
207207
});
208+
209+
test('enabled and with custom log group', () => {
210+
const stack = getTestStack();
211+
const cloudWatchLogGroup = new LogGroup(stack, 'MyLogGroup', {
212+
retention: RetentionDays.FIVE_DAYS,
213+
});
214+
new Trail(stack, 'MyAmazingCloudTrail', {
215+
sendToCloudWatchLogs: true,
216+
cloudWatchLogsRetention: RetentionDays.ONE_WEEK,
217+
cloudWatchLogGroup,
218+
});
219+
220+
expect(stack).toHaveResource('AWS::Logs::LogGroup', {
221+
RetentionInDays: 5,
222+
});
223+
224+
expect(stack).toHaveResource('AWS::CloudTrail::Trail', {
225+
CloudWatchLogsLogGroupArn: stack.resolve(cloudWatchLogGroup.logGroupArn),
226+
});
227+
228+
expect(stack).toHaveResourceLike('AWS::IAM::Policy', {
229+
PolicyDocument: {
230+
Statement: [{
231+
Resource: stack.resolve(cloudWatchLogGroup.logGroupArn),
232+
}],
233+
},
234+
});
235+
});
236+
237+
test('disabled', () => {
238+
const stack = getTestStack();
239+
const t = new Trail(stack, 'MyAmazingCloudTrail', {
240+
sendToCloudWatchLogs: false,
241+
cloudWatchLogsRetention: RetentionDays.ONE_WEEK,
242+
});
243+
expect(t.logGroup).toBeUndefined();
244+
expect(stack).not.toHaveResource('AWS::Logs::LogGroup');
245+
});
208246
});
209247

210248
describe('with event selectors', () => {

0 commit comments

Comments
 (0)