shaikhsalman's picture
refactor: merged structure - model at center, DevSecOps wrapped around it
9d4d5c7 verified
# =============================================================================
# VPC Module — Production-Grade AWS VPC with Security-First Design
# =============================================================================
# Features:
# - Multi-AZ deployment (3 AZs minimum)
# - Private subnets with NAT Gateway egress
# - Public subnets for ALB/NLB only
# - VPC Flow Logs → S3 (encrypted) + CloudWatch
# - IPv6 dual-stack ready
# - Network ACLs (default deny)
# - Dedicated subnets for EKS, RDS, and workloads
# =============================================================================
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
enable_network_address_usage_metrics = true
assign_generated_ipv6_cidr_block = var.enable_ipv6
tags = merge(var.tags, {
Name = "${var.name}-vpc"
})
}
# ---------- Internet Gateway ----------
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(var.tags, {
Name = "${var.name}-igw"
})
}
# ---------- Elastic IPs for NAT Gateways ----------
resource "aws_eip" "nat" {
count = var.nat_gateway_count
domain = "vpc"
tags = merge(var.tags, {
Name = "${var.name}-nat-eip-${count.index + 1}"
})
}
# ---------- Public Subnets (ALB/NLB only) ----------
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = false # Never auto-assign public IPs
tags = merge(var.tags, {
Name = "${var.name}-public-${data.aws_availability_zones.available.names[count.index]}"
Tier = "public"
})
}
# ---------- Private Subnets (EKS Nodes) ----------
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = merge(var.tags, {
Name = "${var.name}-private-${data.aws_availability_zones.available.names[count.index]}"
Tier = "private"
"kubernetes.io/role/internal-elb" = "1"
"kubernetes.io/cluster/${var.eks_cluster_name}" = "shared"
})
}
# ---------- Database Subnets (RDS) ----------
resource "aws_subnet" "database" {
count = length(var.database_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.database_subnet_cidrs[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = merge(var.tags, {
Name = "${var.name}-database-${data.aws_availability_zones.available.names[count.index]}"
Tier = "database"
})
}
# ---------- NAT Gateways ----------
resource "aws_nat_gateway" "this" {
count = var.nat_gateway_count
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.tags, {
Name = "${var.name}-nat-${count.index + 1}"
})
}
# ---------- Route Tables ----------
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
dynamic "route" {
for_each = var.enable_ipv6 ? [1] : []
content {
ipv6_cidr_block = "::/0"
gateway_id = aws_internet_gateway.this.id
}
}
tags = merge(var.tags, {
Name = "${var.name}-public-rt"
})
}
resource "aws_route_table" "private" {
count = var.nat_gateway_count
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.this[count.index].id
}
tags = merge(var.tags, {
Name = "${var.name}-private-rt-${count.index + 1}"
})
}
# ---------- Route Table Associations ----------
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index % var.nat_gateway_count].id
}
# ---------- VPC Flow Logs → S3 ----------
resource "aws_flow_log" "s3" {
vpc_id = aws_vpc.this.id
traffic_type = "ALL"
destination_type = "s3"
destination_arn = var.flow_log_s3_arn
tags = merge(var.tags, {
Name = "${var.name}-flow-log-s3"
})
}
# ---------- VPC Flow Logs → CloudWatch ----------
resource "aws_cloudwatch_log_group" "flow_log" {
name = "/aws/vpc/${var.name}/flow-log"
retention_in_days = var.flow_log_retention_days
tags = merge(var.tags, {
Name = "${var.name}-flow-log-cw"
})
}
resource "aws_iam_role" "flow_log" {
name = "${var.name}-vpc-flow-log-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy" "flow_log" {
name = "${var.name}-vpc-flow-log-policy"
role = aws_iam_role.flow_log.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
]
Resource = "*"
}
]
})
}
resource "aws_flow_log" "cloudwatch" {
vpc_id = aws_vpc.this.id
traffic_type = "ALL"
iam_role_arn = aws_iam_role.flow_log.arn
log_destination = aws_cloudwatch_log_group.flow_log.arn
tags = merge(var.tags, {
Name = "${var.name}-flow-log-cw"
})
}
# ---------- Default Security Group — Deny All ----------
resource "aws_default_security_group" "this" {
vpc_id = aws_vpc.this.id
# No ingress/egress rules = deny all
tags = merge(var.tags, {
Name = "${var.name}-default-sg-locked"
})
}
# ---------- Default Network ACL — Deny All ----------
resource "aws_default_network_acl" "this" {
default_network_acl_id = aws_vpc.this.default_network_acl_id
# No rules = default deny
tags = merge(var.tags, {
Name = "${var.name}-default-nacl-locked"
})
}
# ---------- Data Sources ----------
data "aws_availability_zones" "available" {
state = "available"
}