Skip to content

Commit 88243e5

Browse files
committed
[#316] Add Cloudtrail modules
1 parent f3ca47b commit 88243e5

File tree

19 files changed

+521
-8
lines changed

19 files changed

+521
-8
lines changed

.github/wiki/Security.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ This document provides an overview of the security modules available in the infr
44

55
## Available Security Modules
66

7+
### CloudTrail
8+
9+
The CloudTrail module provides comprehensive API activity logging and monitoring for your AWS infrastructure to enhance security auditing and compliance.
10+
11+
#### Overview
12+
13+
AWS CloudTrail records API calls and events across your AWS account. This module:
14+
15+
- **Comprehensive event logging**: Captures management events, data events, and insight events based on configuration
16+
- **Multi-region support**: Can be configured to log events across all AWS regions for complete visibility
17+
- **CloudWatch integration**: Sends logs to CloudWatch for real-time monitoring and alerting
18+
- **S3 storage**: Stores all CloudTrail logs securely in Amazon S3 with configurable key prefix organization
19+
- **SNS notifications**: Integrates with SNS topics for immediate alerting on critical events
20+
- **Insight events**: Captures unusual activity patterns like API call rate and error rate anomalies
21+
722
### VPC Flow Log
823

924
The VPC Flow Log module captures network traffic information in your VPC to help with security monitoring and network analysis.

src/commands/install/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe('Install add-on command', () => {
103103
it('throws an error', async () => {
104104
expect(stdoutSpy).toHaveBeenCalledWith(
105105
expect.stringContaining(
106-
'Expected invalid to be one of: vpc, securityGroup, alb, bastion, ecr, ecs, cloudwatch, rds, s3, ssm'
106+
'Expected invalid to be one of: vpc, securityGroup, alb, bastion, cloudtrail, ecr, ecs, cloudwatch, rds, s3, ssm, vpcFlowLog'
107107
)
108108
);
109109
});

src/generators/addons/aws/advanced.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { applyAdvancedTemplate } from './advanced';
55
import {
66
applyAwsAlb,
77
applyAwsBastion,
8+
applyAwsCloudtrail,
89
applyAwsEcr,
910
applyAwsEcs,
1011
applyAwsCloudwatch,
@@ -71,6 +72,10 @@ describe('AWS advanced template', () => {
7172
it('does NOT apply VPC Flow Log add-on', () => {
7273
expect(applyAwsVpcFlowLog).not.toHaveBeenCalled();
7374
});
75+
76+
it('does NOT apply CloudTrail add-on', () => {
77+
expect(applyAwsCloudtrail).not.toHaveBeenCalled();
78+
});
7479
});
7580

7681
describe('given enabledSecurityFeatures is true', () => {
@@ -97,6 +102,12 @@ describe('AWS advanced template', () => {
97102
optionsEnabledSecurityFeatures
98103
);
99104
});
105+
106+
it('applies CloudTrail add-on when flag is set', () => {
107+
expect(applyAwsCloudtrail).toHaveBeenCalledWith(
108+
optionsEnabledSecurityFeatures
109+
);
110+
});
100111
});
101112
});
102113
});

src/generators/addons/aws/advanced.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AwsOptions } from '.';
22
import {
33
applyAwsAlb,
44
applyAwsBastion,
5+
applyAwsCloudtrail,
56
applyAwsEcr,
67
applyAwsEcs,
78
applyAwsCloudwatch,
@@ -23,6 +24,7 @@ const applyAdvancedTemplate = async (options: AwsOptions) => {
2324

2425
if (options.enabledSecurityFeatures) {
2526
await applyAwsVpcFlowLog(options);
27+
await applyAwsCloudtrail(options);
2628
}
2729
};
2830

src/generators/addons/aws/dependencies.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AwsOptions } from '@/generators/addons/aws';
55
import {
66
applyAwsAlb,
77
applyAwsBastion,
8+
applyAwsCloudtrail,
89
applyAwsEcr,
910
applyAwsEcs,
1011
applyAwsCloudwatch,
@@ -50,6 +51,12 @@ const AWS_MODULES: Record<AwsModuleName | string, AwsModule> = {
5051
mainContent: 'module "bastion"',
5152
applyModuleFunction: (options: AwsOptions) => applyAwsBastion(options),
5253
},
54+
cloudtrail: {
55+
name: 'cloudtrail',
56+
path: 'modules/cloudtrail',
57+
mainContent: 'module "cloudtrail"',
58+
applyModuleFunction: (options: AwsOptions) => applyAwsCloudtrail(options),
59+
},
5360
ecr: {
5461
name: 'ecr',
5562
path: 'modules/ecr',

src/generators/addons/aws/modules/alb.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import {
1111
INFRA_CORE_MAIN_PATH,
1212
INFRA_CORE_OUTPUTS_PATH,
1313
INFRA_CORE_VARIABLES_PATH,
14-
MODULES_LOCALS_INDICATOR,
1514
} from '@/generators/terraform/constants';
16-
import { appendToFile, copy, injectToFile } from '@/helpers/file';
15+
import { appendToFile, copy } from '@/helpers/file';
1716

1817
import {
1918
AWS_SECURITY_GROUP_MAIN_PATH,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { AwsOptions } from '@/generators/addons/aws';
2+
import { applyTerraformCore } from '@/generators/terraform';
3+
import { remove } from '@/helpers/file';
4+
5+
import applyAwsCloudtrail, {
6+
cloudtrailModuleContent,
7+
cloudtrailOutputsContent,
8+
cloudtrailVariablesContent,
9+
} from './cloudtrail';
10+
import applyTerraformAwsProvider from './core/provider';
11+
12+
jest.mock('inquirer', () => {
13+
return {
14+
prompt: jest.fn().mockResolvedValue({ apply: true }),
15+
};
16+
});
17+
18+
describe('CloudTrail add-on', () => {
19+
describe('given valid AWS options', () => {
20+
const projectDir = 'cloudtrail-addon-test';
21+
22+
beforeAll(async () => {
23+
const awsOptions: AwsOptions = {
24+
projectName: projectDir,
25+
provider: 'aws',
26+
infrastructureType: 'advanced',
27+
};
28+
29+
await applyTerraformCore(awsOptions);
30+
await applyTerraformAwsProvider(awsOptions);
31+
await applyAwsCloudtrail(awsOptions);
32+
});
33+
34+
afterAll(() => {
35+
jest.clearAllMocks();
36+
remove('/', projectDir);
37+
});
38+
39+
it('creates expected files', () => {
40+
const expectedFiles = [
41+
'core/main.tf',
42+
'core/providers.tf',
43+
'core/outputs.tf',
44+
'core/variables.tf',
45+
'modules/cloudtrail/main.tf',
46+
'modules/cloudtrail/variables.tf',
47+
'modules/cloudtrail/outputs.tf',
48+
];
49+
50+
expect(projectDir).toHaveFiles(expectedFiles);
51+
});
52+
53+
it('adds cloudtrail module to main.tf', () => {
54+
expect(projectDir).toHaveContentInFile(
55+
'core/main.tf',
56+
cloudtrailModuleContent
57+
);
58+
});
59+
60+
it('adds cloudtrail variables to variables.tf', () => {
61+
expect(projectDir).toHaveContentInFile(
62+
'core/variables.tf',
63+
cloudtrailVariablesContent
64+
);
65+
});
66+
67+
it('adds cloudtrail outputs to outputs.tf', () => {
68+
expect(projectDir).toHaveContentInFile(
69+
'core/outputs.tf',
70+
cloudtrailOutputsContent
71+
);
72+
});
73+
});
74+
});
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { dedent } from 'ts-dedent';
2+
3+
import { AwsOptions } from '@/generators/addons/aws';
4+
import { isAwsModuleAdded } from '@/generators/addons/aws/dependencies';
5+
import {
6+
INFRA_CORE_MAIN_PATH,
7+
INFRA_CORE_VARIABLES_PATH,
8+
INFRA_CORE_OUTPUTS_PATH,
9+
INFRA_CORE_LOCALS_PATH,
10+
} from '@/generators/terraform/constants';
11+
import { appendToFile, copy } from '@/helpers/file';
12+
13+
import { AWS_TEMPLATE_PATH } from '../constants';
14+
15+
const cloudtrailLocalesContent = dedent`
16+
### Begin CloudTrail ###
17+
locals {
18+
cloudtrail_s3_bucket_policy = {
19+
Version = "2012-10-17"
20+
Statement = [
21+
{
22+
Sid = "AWSCloudTrailAclCheck"
23+
Effect = "Allow"
24+
Principal = {
25+
Service = "cloudtrail.amazonaws.com"
26+
}
27+
Action = [
28+
"s3:GetBucketAcl",
29+
"s3:ListBucket"
30+
]
31+
Resource = "arn:aws:s3:::\${module.cloudtrail_s3_bucket.aws_s3_bucket_name}"
32+
Condition = {
33+
StringEquals = {
34+
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
35+
}
36+
ArnLike = {
37+
"aws:SourceArn" = "arn:aws:cloudtrail:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:*"
38+
}
39+
}
40+
},
41+
{
42+
Sid = "AWSCloudTrailWrite"
43+
Effect = "Allow"
44+
Principal = {
45+
Service = "cloudtrail.amazonaws.com"
46+
}
47+
Action = "s3:PutObject"
48+
Resource = "arn:aws:s3:::\${module.cloudtrail_s3_bucket.aws_s3_bucket_name}/cloudtrail/*"
49+
Condition = {
50+
StringEquals = {
51+
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
52+
"s3:x-amz-acl" = "bucket-owner-full-control"
53+
}
54+
ArnLike = {
55+
"aws:SourceArn" = "arn:aws:cloudtrail:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:*"
56+
}
57+
}
58+
}
59+
]
60+
}
61+
}
62+
### End CloudTrail ###`;
63+
64+
const cloudtrailVariablesContent = dedent`
65+
variable "cloudtrail_log_retention_days" {
66+
description = "The number of days to retain CloudTrail logs in CloudWatch"
67+
type = number
68+
default = 365
69+
}`;
70+
71+
const cloudtrailModuleContent = dedent`
72+
module "cloudtrail_s3_bucket" {
73+
source = "../modules/s3"
74+
75+
env_namespace = local.env_namespace
76+
bucket_name = "\${local.env_namespace}-cloudtrail-logs-\${data.aws_caller_identity.current.account_id}"
77+
force_destroy = true
78+
object_ownership = "BucketOwnerPreferred"
79+
versioning_enabled = true
80+
lifecycle_configuration = {
81+
id = "log-expiration"
82+
status = "Enabled"
83+
filter = {
84+
prefix = ""
85+
}
86+
expiration = {
87+
days = var.cloudtrail_log_retention_days
88+
}
89+
}
90+
}
91+
92+
module "cloudtrail_s3_bucket_policy" {
93+
source = "../modules/s3/bucket_policy"
94+
95+
s3_bucket_name = module.cloudtrail_s3_bucket.aws_s3_bucket_name
96+
s3_bucket_policy = local.cloudtrail_s3_bucket_policy
97+
}
98+
99+
module "cloudtrail_cloudwatch" {
100+
source = "../modules/cloudwatch"
101+
102+
cloud_watch_name = "/aws/cloudtrail/\${local.env_namespace}-cloudtrail"
103+
log_retention_in_days = var.cloudtrail_log_retention_days
104+
}
105+
106+
module "cloudtrail" {
107+
source = "../modules/cloudtrail"
108+
109+
env_namespace = local.env_namespace
110+
trail_name = "\${local.env_namespace}-cloudtrail"
111+
s3_bucket_name = module.cloudtrail_s3_bucket.aws_s3_bucket_name
112+
s3_key_prefix = "cloudtrail"
113+
log_retention_days = var.cloudtrail_log_retention_days
114+
s3_ignore_data_bucket_arns = ["arn:aws:s3:::\${module.cloudtrail_s3_bucket.aws_s3_bucket_name}"]
115+
cloud_watch_arn = module.cloudtrail_cloudwatch.aws_cloudwatch_log_group_arn
116+
}`;
117+
118+
const cloudtrailOutputsContent = dedent`
119+
output "cloudtrail_arn" {
120+
description = "The ARN of the CloudTrail"
121+
value = module.cloudtrail.cloudtrail_arn
122+
}`;
123+
124+
const applyAwsCloudtrail = async (options: AwsOptions) => {
125+
if (isAwsModuleAdded('cloudtrail', options.projectName)) {
126+
return;
127+
}
128+
129+
copy(
130+
`${AWS_TEMPLATE_PATH}/modules/cloudtrail`,
131+
'modules/cloudtrail',
132+
options.projectName
133+
);
134+
appendToFile(
135+
INFRA_CORE_LOCALS_PATH,
136+
cloudtrailLocalesContent,
137+
options.projectName
138+
);
139+
appendToFile(
140+
INFRA_CORE_VARIABLES_PATH,
141+
cloudtrailVariablesContent,
142+
options.projectName
143+
);
144+
appendToFile(
145+
INFRA_CORE_MAIN_PATH,
146+
cloudtrailModuleContent,
147+
options.projectName
148+
);
149+
appendToFile(
150+
INFRA_CORE_OUTPUTS_PATH,
151+
cloudtrailOutputsContent,
152+
options.projectName
153+
);
154+
};
155+
156+
export default applyAwsCloudtrail;
157+
export {
158+
cloudtrailVariablesContent,
159+
cloudtrailModuleContent,
160+
cloudtrailOutputsContent,
161+
};

src/generators/addons/aws/modules/cloudwatch.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ const cloudwatchModuleContent = dedent`
2020
module "cloudwatch" {
2121
source = "../modules/cloudwatch"
2222
23-
env_namespace = local.env_namespace
24-
23+
cloud_watch_name = "\${local.env_namespace}-cloudwatch-log-group"
2524
log_retention_in_days = var.cloudwatch_log_retention_in_days
2625
}`;
2726

src/generators/addons/aws/modules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import applyAwsAlb from './alb';
22
import applyAwsBastion from './bastion';
3+
import applyAwsCloudtrail from './cloudtrail';
34
import applyAwsCloudwatch from './cloudwatch';
45
import applyAwsIamUserAndGroup from './core/iamUserAndGroup';
56
import applyTerraformAwsProvider from './core/provider';
@@ -16,6 +17,7 @@ import applyAwsVpcFlowLog from './vpcFlowLog';
1617
export {
1718
applyAwsAlb,
1819
applyAwsBastion,
20+
applyAwsCloudtrail,
1921
applyTerraformAwsProvider,
2022
applyAwsCloudwatch,
2123
applyAwsEcr,

0 commit comments

Comments
 (0)