1- # VPC and Core Networking
1+ # VPC and Core Networking
22
33resource "aws_vpc" "main" {
44 cidr_block = var. vpc_cidr
@@ -11,6 +11,13 @@ resource "aws_vpc" "main" {
1111 Name = " ${ var . project_prefix } -${ var . environment } -vpc"
1212 }
1313 )
14+
15+ lifecycle {
16+ precondition {
17+ condition = length (var. availability_zones ) == length (var. public_subnet_cidrs ) && length (var. availability_zones ) == length (var. private_subnet_cidrs )
18+ error_message = " The number of items in availability_zones, public_subnet_cidrs, and private_subnet_cidrs lists must be equal."
19+ }
20+ }
1421}
1522
1623resource "aws_internet_gateway" "main" {
@@ -24,7 +31,7 @@ resource "aws_internet_gateway" "main" {
2431 )
2532}
2633
27- # Subnets
34+ # Subnets
2835
2936# Deploys a public and private subnet into each specified Availability Zone.
3037
@@ -57,7 +64,7 @@ resource "aws_subnet" "private" {
5764 )
5865}
5966
60- # Routing and NAT Gateway for Private Subnets
67+ # Routing and NAT Gateway for Private Subnets
6168
6269# We create a SINGLE NAT Gateway and a SINGLE private route table. This is a cost
6370# optimization but introduces a single-AZ egress SPOF compared to per-AZ NAT gateways.
@@ -126,15 +133,58 @@ resource "aws_route_table" "private" {
126133 }
127134 )
128135}
129-
130136# Associate the single private route table with all private subnets.
131137resource "aws_route_table_association" "private" {
132138 count = length (aws_subnet. private )
133139 subnet_id = aws_subnet. private [count . index ]. id
134140 route_table_id = aws_route_table. private . id
135141}
136142
137- # S3 Bucket for ALB Access Logs
143+ # --- VPC Flow Logs ---
144+ # Added VPC Flow Logs to address the Trivy finding.
145+
146+ resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
147+ name = " /aws/vpc-flow-logs/${ var . project_prefix } -${ var . environment } "
148+ retention_in_days = 30 # Or a configurable variable
149+
150+ tags = merge (var. tags , { Name = " ${ var . project_prefix } -${ var . environment } -vpc-flow-logs-lg" })
151+ }
152+
153+ resource "aws_iam_role" "vpc_flow_logs" {
154+ name = " ${ var . project_prefix } -${ var . environment } -vpc-flow-logs-role"
155+ assume_role_policy = jsonencode ({
156+ Version = " 2012-10-17"
157+ Statement = [
158+ {
159+ Action = " sts:AssumeRole"
160+ Effect = " Allow"
161+ Principal = {
162+ Service = " vpc-flow-logs.amazonaws.com"
163+ }
164+ },
165+ ]
166+ })
167+ tags = merge (var. tags , { Name = " ${ var . project_prefix } -${ var . environment } -vpc-flow-logs-role" })
168+ }
169+
170+ resource "aws_iam_role_policy_attachment" "vpc_flow_logs" {
171+ role = aws_iam_role. vpc_flow_logs . name
172+ policy_arn = " arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" # Scoped down for production if needed
173+ }
174+
175+ resource "aws_flow_log" "main" {
176+ iam_role_arn = aws_iam_role. vpc_flow_logs . arn
177+ log_destination = aws_cloudwatch_log_group. vpc_flow_logs . arn
178+ traffic_type = " ALL"
179+ vpc_id = aws_vpc. main . id
180+
181+ tags = merge (var. tags , { Name = " ${ var . project_prefix } -${ var . environment } -vpc-flow-log" })
182+
183+ depends_on = [aws_iam_role_policy_attachment . vpc_flow_logs ]
184+ }
185+
186+
187+ # S3 Bucket for ALB Access Logs
138188
139189# This data source gets the AWS Account ID for the ELB service in the current region.
140190data "aws_elb_service_account" "current" {}
@@ -221,7 +271,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "alb_access_logs"
221271 apply_server_side_encryption_by_default {
222272 sse_algorithm = " AES256"
223273 }
224-
274+
225275 }
226276}
227277
@@ -233,11 +283,11 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "s3_server_access_
233283 apply_server_side_encryption_by_default {
234284 sse_algorithm = " AES256"
235285 }
236-
286+
237287 }
238288}
239289
240- # BUCKETS LIFECYCLE POLICIES
290+ # BUCKETS LIFECYCLE POLICIES
241291resource "aws_s3_bucket_lifecycle_configuration" "alb_access_logs" {
242292 count = var. enable_alb_access_logs ? 1 : 0
243293 bucket = aws_s3_bucket. alb_access_logs [0 ]. id
@@ -341,9 +391,9 @@ data "aws_iam_policy_document" "alb_access_logs" {
341391
342392 # This statement denies any access to the bucket over insecure HTTP.
343393 statement {
344- sid = " DenyInsecureTransport"
345- effect = " Deny"
346- actions = [" s3:*" ]
394+ sid = " DenyInsecureTransport"
395+ effect = " Deny"
396+ actions = [" s3:*" ]
347397 resources = [
348398 aws_s3_bucket . alb_access_logs [0 ]. arn ,
349399 " ${ aws_s3_bucket . alb_access_logs [0 ]. arn } /*"
@@ -389,8 +439,8 @@ data "aws_iam_policy_document" "s3_server_access_logs" {
389439
390440 # Deny any access over insecure HTTP
391441 statement {
392- sid = " DenyInsecureTransport"
393- effect = " Deny"
442+ sid = " DenyInsecureTransport"
443+ effect = " Deny"
394444 actions = [" s3:*" ]
395445 resources = [
396446 aws_s3_bucket . s3_server_access_logs [0 ]. arn ,
@@ -430,7 +480,7 @@ resource "aws_s3_bucket_policy" "s3_server_access_logs" {
430480 ]
431481}
432482
433- # Application Load Balancer
483+ # Application Load Balancer
434484
435485resource "aws_security_group" "alb" {
436486 name = " ${ var . project_prefix } -${ var . environment } -alb-sg"
@@ -454,11 +504,12 @@ resource "aws_security_group" "alb" {
454504 description = " Allow HTTPS traffic from anywhere"
455505 }
456506
507+ # Restrict egress to within the VPC. The ALB now only talk to internal targets (like ECS), not the entire internet.
457508 egress {
458509 protocol = " -1"
459510 from_port = 0
460511 to_port = 0
461- cidr_blocks = [" 0.0.0.0/0 " ]
512+ cidr_blocks = [var . vpc_cidr ]
462513 description = " Allow all outbound traffic"
463514 }
464515
@@ -471,11 +522,11 @@ resource "aws_security_group" "alb" {
471522}
472523
473524resource "aws_lb" "main" {
474- name = " ${ var . project_prefix } -${ var . environment } -alb"
475- internal = false
476- load_balancer_type = " application"
477- security_groups = [aws_security_group . alb . id ]
478- subnets = aws_subnet. public [* ]. id
525+ name = " ${ var . project_prefix } -${ var . environment } -alb"
526+ internal = false
527+ load_balancer_type = " application"
528+ security_groups = [aws_security_group . alb . id ]
529+ subnets = aws_subnet. public [* ]. id
479530 drop_invalid_header_fields = true
480531
481532 # Deletion protection should be enabled via a variable for production.
@@ -533,4 +584,4 @@ resource "aws_lb_listener" "https" {
533584 status_code = " 404"
534585 }
535586 }
536- }
587+ }
0 commit comments