Skip to content

Commit 73b05d7

Browse files
committed
[#316] Add VPC flow log module
1 parent bba0625 commit 73b05d7

File tree

15 files changed

+581
-9
lines changed

15 files changed

+581
-9
lines changed

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
nodejs 18.12.1
2-
terraform 1.8.3
2+
terraform 1.13.3
33
trivy 0.47.0

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
applyAwsRds,
1212
applyAwsS3,
1313
applyAwsSsm,
14+
applyAwsVpcFlowLog,
1415
} from './modules';
1516

1617
jest.mock('./modules');
@@ -65,5 +66,37 @@ describe('AWS advanced template', () => {
6566
it('applies ECS add-on', () => {
6667
expect(applyAwsEcs).toHaveBeenCalledWith(options);
6768
});
69+
70+
describe('given enabledSecurityFeatures is not set', () => {
71+
it('does NOT apply VPC Flow Log add-on', () => {
72+
expect(applyAwsVpcFlowLog).not.toHaveBeenCalled();
73+
});
74+
});
75+
76+
describe('given enabledSecurityFeatures is true', () => {
77+
const optionsEnabledSecurityFeatures: AwsOptions = {
78+
projectName: projectDir,
79+
provider: 'aws',
80+
infrastructureType: 'advanced',
81+
awsRegion: 'ap-southeast-1',
82+
enabledSecurityFeatures: true,
83+
};
84+
85+
beforeAll(async () => {
86+
jest.clearAllMocks();
87+
await applyAdvancedTemplate(optionsEnabledSecurityFeatures);
88+
});
89+
90+
afterAll(() => {
91+
jest.clearAllMocks();
92+
remove('/', projectDir);
93+
});
94+
95+
it('applies VPC Flow Log add-on when flag is set', () => {
96+
expect(applyAwsVpcFlowLog).toHaveBeenCalledWith(
97+
optionsEnabledSecurityFeatures
98+
);
99+
});
100+
});
68101
});
69102
});

src/generators/addons/aws/advanced.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
applyAwsRds,
99
applyAwsS3,
1010
applyAwsSsm,
11+
applyAwsVpcFlowLog,
1112
} from './modules';
1213

1314
const applyAdvancedTemplate = async (options: AwsOptions) => {
@@ -19,6 +20,10 @@ const applyAdvancedTemplate = async (options: AwsOptions) => {
1920
await applyAwsCloudwatch(options);
2021
await applyAwsS3(options);
2122
await applyAwsSsm(options);
23+
24+
if (options.enabledSecurityFeatures) {
25+
await applyAwsVpcFlowLog(options);
26+
}
2227
};
2328

2429
export { applyAdvancedTemplate };

src/generators/addons/aws/dependencies.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
applyAwsSecurityGroup,
1414
applyAwsSsm,
1515
applyAwsVpc,
16+
applyAwsVpcFlowLog,
1617
} from '@/generators/addons/aws/modules';
1718
import { containsContent, isExisting } from '@/helpers/file';
1819

@@ -85,6 +86,12 @@ const AWS_MODULES: Record<AwsModuleName | string, AwsModule> = {
8586
mainContent: 'module "ssm"',
8687
applyModuleFunction: (options: AwsOptions) => applyAwsSsm(options),
8788
},
89+
vpcFlowLog: {
90+
name: 'vpcFlowLog',
91+
path: 'modules/vpc_flow_log',
92+
mainContent: 'module "vpc_flow_log"',
93+
applyModuleFunction: (options: AwsOptions) => applyAwsVpcFlowLog(options),
94+
},
8895
};
8996

9097
const isAwsModuleAdded = (

src/generators/addons/aws/index.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import {
1212
applyAwsVpc,
1313
} from './modules';
1414

15+
type AwsOptions = GeneralOptions & {
16+
infrastructureType?: 'blank' | 'advanced';
17+
awsRegion?: string;
18+
enabledSecurityFeatures?: boolean;
19+
};
20+
1521
const awsChoices = [
1622
{
1723
type: 'list',
@@ -30,6 +36,14 @@ const awsChoices = [
3036
},
3137
],
3238
},
39+
{
40+
type: 'confirm',
41+
name: 'enabledSecurityFeatures',
42+
message:
43+
'Do you want to create (VPC Flow Logs + CloudTrail) to enhance security posture and compliance?',
44+
default: false,
45+
when: (answers: AwsOptions) => answers.infrastructureType === 'advanced',
46+
},
3347
{
3448
type: 'input',
3549
name: 'awsRegion',
@@ -38,11 +52,6 @@ const awsChoices = [
3852
},
3953
];
4054

41-
type AwsOptions = GeneralOptions & {
42-
infrastructureType?: 'blank' | 'advanced';
43-
awsRegion?: string;
44-
};
45-
4655
const applyProviderAndRegion = async (options: AwsOptions) => {
4756
await applyTerraformAwsProvider(options);
4857
await applyAwsRegion(options);
@@ -52,10 +61,12 @@ const generateAwsTemplate = async (
5261
generalOptions: GeneralOptions
5362
): Promise<void> => {
5463
const awsOptionsPrompt = await prompt(awsChoices);
64+
5565
const awsOptions: AwsOptions = {
5666
...generalOptions,
5767
infrastructureType: awsOptionsPrompt.infrastructureType,
5868
awsRegion: awsOptionsPrompt.awsRegion,
69+
enabledSecurityFeatures: awsOptionsPrompt.enabledSecurityFeatures,
5970
};
6071

6172
switch (awsOptions.infrastructureType) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import applyAwsEcs from './ecs';
1111
import applyAwsRds from './rds';
1212
import applyAwsS3 from './s3';
1313
import applyAwsSsm from './ssm';
14+
import applyAwsVpcFlowLog from './vpcFlowLog';
1415

1516
export {
1617
applyAwsAlb,
@@ -26,4 +27,5 @@ export {
2627
applyAwsSecurityGroup,
2728
applyAwsSsm,
2829
applyAwsVpc,
30+
applyAwsVpcFlowLog,
2931
};
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 applyTerraformAwsProvider from './core/provider';
6+
import applyAwsVpcFlowLog, {
7+
vpcFlowLogModuleContent,
8+
vpcFlowLogOutputsContent,
9+
vpcFlowLogVariablesContent,
10+
} from './vpcFlowLog';
11+
12+
jest.mock('inquirer', () => {
13+
return {
14+
prompt: jest.fn().mockResolvedValue({ apply: true }),
15+
};
16+
});
17+
18+
describe('VPC Flow Log add-on', () => {
19+
describe('given valid AWS options', () => {
20+
const projectDir = 'vpc-flow-log-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 applyAwsVpcFlowLog(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/vpc_flow_log/main.tf',
46+
'modules/vpc_flow_log/variables.tf',
47+
'modules/vpc_flow_log/outputs.tf',
48+
];
49+
50+
expect(projectDir).toHaveFiles(expectedFiles);
51+
});
52+
53+
it('adds vpc_flow_log module to main.tf', () => {
54+
expect(projectDir).toHaveContentInFile(
55+
'core/main.tf',
56+
vpcFlowLogModuleContent
57+
);
58+
});
59+
60+
it('adds vpc_flow_log variables to variables.tf', () => {
61+
expect(projectDir).toHaveContentInFile(
62+
'core/variables.tf',
63+
vpcFlowLogVariablesContent
64+
);
65+
});
66+
67+
it('adds vpc_flow_log outputs to outputs.tf', () => {
68+
expect(projectDir).toHaveContentInFile(
69+
'core/outputs.tf',
70+
vpcFlowLogOutputsContent
71+
);
72+
});
73+
});
74+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { dedent } from 'ts-dedent';
2+
3+
import { AwsOptions } from '@/generators/addons/aws';
4+
import {
5+
isAwsModuleAdded,
6+
requireAwsModules,
7+
} from '@/generators/addons/aws/dependencies';
8+
import {
9+
INFRA_CORE_MAIN_PATH,
10+
INFRA_CORE_VARIABLES_PATH,
11+
INFRA_CORE_OUTPUTS_PATH,
12+
} from '@/generators/terraform/constants';
13+
import { appendToFile, copy } from '@/helpers/file';
14+
15+
import { AWS_TEMPLATE_PATH } from '../constants';
16+
17+
const vpcFlowLogVariablesContent = dedent`
18+
variable "vpc_flow_log_s3_bucket_name" {
19+
description = "The name of the S3 bucket to store VPC Flow Logs"
20+
type = string
21+
}
22+
23+
variable "vpc_flow_log_retention_days" {
24+
description = "The number of days to retain VPC Flow Logs in S3"
25+
type = number
26+
default = 90
27+
}`;
28+
29+
const vpcFlowLogModuleContent = dedent`
30+
module "vpc_flow_log" {
31+
source = "../modules/vpc_flow_log"
32+
33+
env_namespace = local.env_namespace
34+
vpc_id = module.vpc.vpc_id
35+
s3_bucket_name = var.vpc_flow_log_s3_bucket_name
36+
log_retention_days = var.vpc_flow_log_retention_days
37+
}`;
38+
39+
const vpcFlowLogOutputsContent = dedent`
40+
output "vpc_flow_log_s3_bucket_id" {
41+
description = "The ID of the S3 bucket to store VPC Flow Logs"
42+
value = module.vpc_flow_log.s3_bucket_id
43+
}
44+
45+
output "vpc_flow_log_s3_bucket_name" {
46+
description = "The name of the S3 bucket to store VPC Flow Logs"
47+
value = module.vpc_flow_log.s3_bucket_name
48+
}
49+
50+
output "vpc_flow_log_arn" {
51+
description = "The ARN of the VPC Flow Log"
52+
value = module.vpc_flow_log.vpc_flow_log_arn
53+
}
54+
55+
output "vpc_flow_log_athena_table_name" {
56+
description = "The name of the Athena table for VPC Flow Logs"
57+
value = module.vpc_flow_log.vpc_flow_log_athena_table_name
58+
}
59+
60+
output "vpc_flow_log_athena_table_arn" {
61+
description = "The arn of the Athena table for VPC Flow Logs"
62+
value = module.vpc_flow_log.vpc_flow_log_athena_table_arn
63+
}`;
64+
65+
const applyAwsVpcFlowLog = async (options: AwsOptions) => {
66+
if (isAwsModuleAdded('vpcFlowLog', options.projectName)) {
67+
return;
68+
}
69+
await requireAwsModules('vpcFlowLog', 'vpc', options);
70+
71+
copy(
72+
`${AWS_TEMPLATE_PATH}/modules/vpc_flow_log`,
73+
'modules/vpc_flow_log',
74+
options.projectName
75+
);
76+
appendToFile(
77+
INFRA_CORE_VARIABLES_PATH,
78+
vpcFlowLogVariablesContent,
79+
options.projectName
80+
);
81+
appendToFile(
82+
INFRA_CORE_MAIN_PATH,
83+
vpcFlowLogModuleContent,
84+
options.projectName
85+
);
86+
appendToFile(
87+
INFRA_CORE_OUTPUTS_PATH,
88+
vpcFlowLogOutputsContent,
89+
options.projectName
90+
);
91+
};
92+
93+
export default applyAwsVpcFlowLog;
94+
export {
95+
vpcFlowLogVariablesContent,
96+
vpcFlowLogModuleContent,
97+
vpcFlowLogOutputsContent,
98+
};

src/generators/terraform/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const awsModules = [
1111
'rds',
1212
's3',
1313
'ssm',
14+
'vpcFlowLog',
1415
] as const;
1516

1617
type AwsModuleName = (typeof awsModules)[number] | string;

0 commit comments

Comments
 (0)