Skip to content

Commit 8f894f4

Browse files
committed
[#316] Add VPC flow log module
1 parent 09a4f40 commit 8f894f4

File tree

16 files changed

+508
-6
lines changed

16 files changed

+508
-6
lines changed

.github/wiki/Security.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Security
2+
3+
This document provides an overview of the security modules available in the infrastructure templates to help enhance your AWS infrastructure security posture.
4+
5+
## Available Security Modules
6+
7+
### VPC Flow Log
8+
9+
The VPC Flow Log module captures network traffic information in your VPC to help with security monitoring and network analysis.
10+
11+
#### Overview
12+
13+
VPC Flow Logs capture information about IP traffic going to and from network interfaces in your VPC. This module:
14+
15+
- **Captures network flows**: Records detailed information about network traffic patterns in your VPC
16+
- **Stores logs in S3**: Saves all captured flow logs securely in an Amazon S3 bucket with configurable retention
17+
- **Enables querying via Athena**: Provides ready-to-use AWS Athena integration for analyzing log data using SQL queries
18+
19+
## Getting Started
20+
21+
To add security features to your infrastructure:
22+
23+
1. Run the infrastructure generator
24+
2. Select "Complete infrastructure (VPC + ECR + RDS + S3 + FARGATE + Cloudwatch + Security groups + ALB)" when prompted for infrastructure type
25+
3. Choose "Yes" when asked "Do you want to create (VPC Flow Logs + CloudTrail) to enhance security posture and compliance?"
26+

.github/wiki/_Sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [[Add a new CLI command | Add new command]]
1313
- [[Add New Addon or Module | Add new addon module]]
1414
- [[Using The Generator As Reference]]
15+
- [[Security]]
1516
- [[Testing]]
1617
- [[Modify the Infrastructure Diagram | Modify infra diagram]]
1718
- [[Publishing]]

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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
vpcFlowLogVariablesContent,
9+
} from './vpcFlowLog';
10+
11+
jest.mock('inquirer', () => {
12+
return {
13+
prompt: jest.fn().mockResolvedValue({ apply: true }),
14+
};
15+
});
16+
17+
describe('VPC Flow Log add-on', () => {
18+
describe('given valid AWS options', () => {
19+
const projectDir = 'vpc-flow-log-addon-test';
20+
21+
beforeAll(async () => {
22+
const awsOptions: AwsOptions = {
23+
projectName: projectDir,
24+
provider: 'aws',
25+
infrastructureType: 'advanced',
26+
};
27+
28+
await applyTerraformCore(awsOptions);
29+
await applyTerraformAwsProvider(awsOptions);
30+
await applyAwsVpcFlowLog(awsOptions);
31+
});
32+
33+
afterAll(() => {
34+
jest.clearAllMocks();
35+
remove('/', projectDir);
36+
});
37+
38+
it('creates expected files', () => {
39+
const expectedFiles = [
40+
'core/main.tf',
41+
'core/providers.tf',
42+
'core/outputs.tf',
43+
'core/variables.tf',
44+
'modules/vpc_flow_log/main.tf',
45+
'modules/vpc_flow_log/variables.tf',
46+
'modules/vpc_flow_log/outputs.tf',
47+
];
48+
49+
expect(projectDir).toHaveFiles(expectedFiles);
50+
});
51+
52+
it('adds vpc_flow_log module to main.tf', () => {
53+
expect(projectDir).toHaveContentInFile(
54+
'core/main.tf',
55+
vpcFlowLogModuleContent
56+
);
57+
});
58+
59+
it('adds vpc_flow_log variables to variables.tf', () => {
60+
expect(projectDir).toHaveContentInFile(
61+
'core/variables.tf',
62+
vpcFlowLogVariablesContent
63+
);
64+
});
65+
});
66+
});
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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_LOCALS_PATH,
10+
INFRA_CORE_MAIN_PATH,
11+
INFRA_CORE_VARIABLES_PATH,
12+
MODULES_LOCALS_INDICATOR,
13+
} from '@/generators/terraform/constants';
14+
import { appendToFile, copy, injectToFile } from '@/helpers/file';
15+
16+
import { AWS_TEMPLATE_PATH } from '../constants';
17+
18+
const vpcFlowLogLocalesContent = ` ###VPC Flow Log###
19+
vpc_flow_log_s3_bucket_policy = {
20+
Version = "2012-10-17"
21+
Statement = [
22+
{
23+
Sid = "AWSLogDeliveryWrite"
24+
Effect = "Allow"
25+
Principal = {
26+
Service = "delivery.logs.amazonaws.com"
27+
}
28+
Action = "s3:PutObject"
29+
Resource = "arn:aws:s3:::\${module.s3_flow_log.aws_s3_bucket_name}/*"
30+
Condition = {
31+
StringEquals = {
32+
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
33+
"s3:x-amz-acl" = "bucket-owner-full-control"
34+
}
35+
ArnLike = {
36+
"aws:SourceArn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:*"
37+
}
38+
}
39+
},
40+
{
41+
Sid = "AWSLogDeliveryAclCheck"
42+
Effect = "Allow"
43+
Principal = {
44+
Service = "delivery.logs.amazonaws.com"
45+
}
46+
Action = [
47+
"s3:GetBucketAcl",
48+
"s3:ListBucket"
49+
]
50+
Resource = "arn:aws:s3:::\${module.s3_flow_log.aws_s3_bucket_name}"
51+
Condition = {
52+
StringEquals = {
53+
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
54+
}
55+
ArnLike = {
56+
"aws:SourceArn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:*"
57+
}
58+
}
59+
},
60+
{
61+
Effect = "Deny"
62+
Principal = "*"
63+
Action = "s3:*"
64+
Resource = [
65+
"arn:aws:s3:::\${module.s3_flow_log.aws_s3_bucket_name}",
66+
"arn:aws:s3:::\${module.s3_flow_log.aws_s3_bucket_name}/*"
67+
]
68+
Condition = {
69+
Bool = {
70+
"aws:SecureTransport" = "false"
71+
}
72+
}
73+
}
74+
]
75+
}`;
76+
77+
const vpcFlowLogVariablesContent = dedent`
78+
variable "vpc_flow_log_retention_days" {
79+
description = "The number of days to retain VPC Flow Logs in S3"
80+
type = number
81+
default = 90
82+
}`;
83+
84+
const vpcFlowLogModuleContent = dedent`
85+
module "s3_flow_log" {
86+
source = "../modules/s3"
87+
88+
env_namespace = local.env_namespace
89+
bucket_name = "\${local.env_namespace}-flow-logs-\${data.aws_caller_identity.current.account_id}"
90+
force_destroy = true
91+
object_ownership = "BucketOwnerPreferred"
92+
}
93+
94+
module "s3_flow_log_policy" {
95+
source = "../modules/s3/bucket_policy"
96+
97+
s3_bucket_name = module.s3_flow_log.aws_s3_bucket_name
98+
s3_bucket_policy = local.vpc_flow_log_s3_bucket_policy
99+
}
100+
101+
module "vpc_flow_log" {
102+
source = "../modules/vpc_flow_log"
103+
104+
env_namespace = local.env_namespace
105+
vpc_id = module.vpc.vpc_id
106+
s3_bucket_name = module.s3_flow_log.aws_s3_bucket_name
107+
log_retention_days = var.vpc_flow_log_retention_days
108+
}`;
109+
110+
const applyAwsVpcFlowLog = async (options: AwsOptions) => {
111+
if (isAwsModuleAdded('vpcFlowLog', options.projectName)) {
112+
return;
113+
}
114+
await requireAwsModules('vpcFlowLog', 'vpc', options);
115+
116+
copy(
117+
`${AWS_TEMPLATE_PATH}/modules/vpc_flow_log`,
118+
'modules/vpc_flow_log',
119+
options.projectName
120+
);
121+
injectToFile(
122+
INFRA_CORE_LOCALS_PATH,
123+
vpcFlowLogLocalesContent,
124+
options.projectName,
125+
{
126+
insertAfter: MODULES_LOCALS_INDICATOR,
127+
}
128+
);
129+
appendToFile(
130+
INFRA_CORE_VARIABLES_PATH,
131+
vpcFlowLogVariablesContent,
132+
options.projectName
133+
);
134+
appendToFile(
135+
INFRA_CORE_MAIN_PATH,
136+
vpcFlowLogModuleContent,
137+
options.projectName
138+
);
139+
};
140+
141+
export default applyAwsVpcFlowLog;
142+
export { vpcFlowLogModuleContent, vpcFlowLogVariablesContent };

0 commit comments

Comments
 (0)