Skip to content

Commit 58c2f3c

Browse files
committed
fix(ci): Pass all local pre-commit and security checks
This commit resolves all failing CI checks from the initial PR. - Updates the cspell dictionary to recognize common IaC acronyms. - Configures .pre-commit-config.yaml to correctly run Terraform, TFLint, and Trivy hooks. - Updates trivyignore.yaml to manage acceptable risks for the PoC. - Applies all automatic formatting fixes from the linting tools. - Edited .gitignore for Terraform-specific files (.terraform/, *.tfstate).
1 parent ad53277 commit 58c2f3c

File tree

7 files changed

+102
-29
lines changed

7 files changed

+102
-29
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ __pycache__
1717
.python_history
1818
.python-version
1919
.ruff_cache
20+
**/.terraform/
21+
**/.terraform.lock.hcl
22+
*.tfstate
23+
*.tfstate.backup
24+
*.tfvars
25+
!*.tfvars.example
2026
.venv/
2127
.vscode
2228
*.code-workspace

.pre-commit-config.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ repos:
8888
- id: pyproject-fmt
8989

9090
- repo: https://github.com/antonbabenko/pre-commit-terraform
91-
rev: v1.92.0
91+
rev: v1.92.0
9292
hooks:
9393
- id: terraform_fmt
9494
files: \.tf$
@@ -98,3 +98,5 @@ repos:
9898
files: \.tf$
9999
- id: terraform_trivy
100100
files: \.tf$
101+
args:
102+
- --args=--ignorefile=../../../trivyignore.yaml

Terraform/modules/01-Network/main.tf

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# VPC and Core Networking
1+
# VPC and Core Networking
22

33
resource "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

1623
resource "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.
131137
resource "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.
140190
data "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
241291
resource "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

435485
resource "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

473524
resource "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+
}

Terraform/modules/01-Network/outputs.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ output "alb_dns_name" {
2626
output "alb_https_listener_arn" {
2727
description = "The ARN of the ALB's HTTPS listener."
2828
value = aws_lb_listener.https.arn
29-
}
29+
}

Terraform/modules/01-Network/variables.tf

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ variable "environment" {
1515
error_message = "The environment must be one of: dev, staging, prod."
1616
}
1717
}
18-
1918
variable "vpc_cidr" {
2019
description = "The CIDR block for the VPC."
2120
type = string
@@ -31,16 +30,16 @@ variable "public_subnet_cidrs" {
3130
description = "A list of CIDR blocks for the public subnets. The number of CIDRs must match the number of availability_zones."
3231
type = list(string)
3332
validation {
34-
condition = length(var.public_subnet_cidrs) > 0 && length(var.public_subnet_cidrs) == length(var.availability_zones)
35-
error_message = "Provide at least one public subnet CIDR, and ensure its count matches availability_zones."
36-
}
33+
condition = length(var.public_subnet_cidrs) > 0
34+
error_message = "Provide at least one public subnet CIDR, and ensure its count matches availability_zones."
35+
}
3736
}
3837

3938
variable "private_subnet_cidrs" {
4039
description = "A list of CIDR blocks for the private subnets. The number of CIDRs must match the number of availability_zones."
4140
type = list(string)
4241
validation {
43-
condition = length(var.private_subnet_cidrs) > 0 && length(var.private_subnet_cidrs) == length(var.availability_zones)
42+
condition = length(var.private_subnet_cidrs) > 0
4443
error_message = "Provide at least one private subnet CIDR, and ensure its count matches availability_zones."
4544
}
4645
}
@@ -67,4 +66,4 @@ variable "alb_access_logs_bucket_name" {
6766
description = "The name of the S3 bucket to store ALB access logs. Must be globally unique. If left empty, a name will be generated."
6867
type = string
6968
default = ""
70-
}
69+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
required_version = ">= 1.3.0"
3+
4+
required_providers {
5+
aws = {
6+
source = "hashicorp/aws"
7+
version = "~> 6.14.1"
8+
}
9+
}
10+
}

trivyignore.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ vulnerabilities:
77
- id: CVE-2025-27113 # libxml2 null dereference
88
- id: CVE-2025-31115 # xz heap use-after-free
99
- id: CVE-2025-6965 # sqlite integer truncation
10+
misconfigurations:
11+
- id: AVD-AWS-0053 # AVD-AWS-0053: Ignoring because the ALB is intentionally public-facing.
12+
- id: AVD-AWS-0132 # AVD-AWS-0132: Ignoring because AWS-managed keys (AES256) are acceptable for this PoC.
13+
- id: AVD-AWS-0164 # AVD-AWS-0164: Ignoring because public subnets are required for the ALB and NAT Gateway.
14+
- id: AVD-AWS-0017 # AVD-AWS-0017: Ignoring because default CloudWatch encryption is acceptable for this PoC.

0 commit comments

Comments
 (0)