Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions modules/aws/vpc/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
terraform {
required_version = ">= 1.14.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.65.0"
}
}
}

locals {
az_keys = { for i, az in var.availability_zones : az => i }

# When single_nat_gateway is true, all private subnets route through the first AZ's NAT.
# Otherwise, each AZ gets its own NAT gateway.
nat_az_keys = var.enable_nat_gateway ? (
var.single_nat_gateway ? { (var.availability_zones[0]) = 0 } : local.az_keys
) : {}
}

# ------------------------------------------------------------------------------
# VPC
# ------------------------------------------------------------------------------

resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true

tags = {
Name = var.name
}
}

# ------------------------------------------------------------------------------
# Internet Gateway
# ------------------------------------------------------------------------------

resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id

tags = {
Name = "${var.name}-igw"
}
}

# ------------------------------------------------------------------------------
# Public Subnets
# ------------------------------------------------------------------------------

resource "aws_subnet" "public" {
for_each = local.az_keys

vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[each.value]
availability_zone = each.key
map_public_ip_on_launch = var.map_public_ip_on_launch

tags = {
Name = "${var.name}-public-${each.key}"
}
}

resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id

tags = {
Name = "${var.name}-public"
}
}

resource "aws_route" "public_internet" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}

resource "aws_route_table_association" "public" {
for_each = aws_subnet.public

subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}

# ------------------------------------------------------------------------------
# Private Subnets
# ------------------------------------------------------------------------------

resource "aws_subnet" "private" {
for_each = local.az_keys

vpc_id = aws_vpc.this.id
cidr_block = var.private_subnet_cidrs[each.value]
availability_zone = each.key

tags = {
Name = "${var.name}-private-${each.key}"
}
}

resource "aws_route_table" "private" {
for_each = local.az_keys

vpc_id = aws_vpc.this.id

tags = {
Name = "${var.name}-private-${each.key}"
}
}

resource "aws_route_table_association" "private" {
for_each = aws_subnet.private

subnet_id = each.value.id
route_table_id = aws_route_table.private[each.key].id
}

# ------------------------------------------------------------------------------
# NAT Gateway (optional)
# ------------------------------------------------------------------------------

resource "aws_eip" "nat" {
for_each = local.nat_az_keys

domain = "vpc"

tags = {
Name = "${var.name}-nat-${each.key}"
}
}

resource "aws_nat_gateway" "this" {
for_each = local.nat_az_keys

allocation_id = aws_eip.nat[each.key].id
subnet_id = aws_subnet.public[each.key].id

tags = {
Name = "${var.name}-nat-${each.key}"
}
}

resource "aws_route" "private_nat" {
for_each = var.enable_nat_gateway ? local.az_keys : {}

route_table_id = aws_route_table.private[each.key].id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = var.single_nat_gateway ? (
aws_nat_gateway.this[var.availability_zones[0]].id
) : aws_nat_gateway.this[each.key].id
}

# ------------------------------------------------------------------------------
# VPC Flow Logs
# ------------------------------------------------------------------------------

resource "aws_cloudwatch_log_group" "flow_log" {
name = "/aws/vpc/${var.name}/flow-logs"
retention_in_days = var.flow_log_retention_days

tags = {
Name = "${var.name}-flow-logs"
}
}

resource "aws_iam_role" "flow_log" {
name = "${var.name}-vpc-flow-log"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})

tags = {
Name = "${var.name}-vpc-flow-log"
}
}

resource "aws_iam_role_policy" "flow_log" {
name = "vpc-flow-log-publish"
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 = "${aws_cloudwatch_log_group.flow_log.arn}:*"
}]
})
}

resource "aws_flow_log" "this" {
vpc_id = aws_vpc.this.id
traffic_type = var.flow_log_traffic_type
log_destination = aws_cloudwatch_log_group.flow_log.arn
log_destination_type = "cloud-watch-logs"
iam_role_arn = aws_iam_role.flow_log.arn

tags = {
Name = "${var.name}-flow-log"
}
}
59 changes: 59 additions & 0 deletions modules/aws/vpc/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
output "vpc_id" {
description = "ID of the VPC."
value = aws_vpc.this.id
}

output "vpc_cidr_block" {
description = "CIDR block of the VPC."
value = aws_vpc.this.cidr_block
}

output "public_subnet_ids" {
description = "List of public subnet IDs."
value = [for s in aws_subnet.public : s.id]
}

output "private_subnet_ids" {
description = "List of private subnet IDs."
value = [for s in aws_subnet.private : s.id]
}

output "public_subnet_cidrs" {
description = "List of public subnet CIDR blocks."
value = [for s in aws_subnet.public : s.cidr_block]
}

output "private_subnet_cidrs" {
description = "List of private subnet CIDR blocks."
value = [for s in aws_subnet.private : s.cidr_block]
}

output "internet_gateway_id" {
description = "ID of the internet gateway."
value = aws_internet_gateway.this.id
}

output "nat_gateway_ids" {
description = "List of NAT gateway IDs. Empty when enable_nat_gateway is false."
value = [for ng in aws_nat_gateway.this : ng.id]
}

output "public_route_table_id" {
description = "ID of the public route table."
value = aws_route_table.public.id
}

output "private_route_table_ids" {
description = "List of private route table IDs."
value = [for rt in aws_route_table.private : rt.id]
}

output "flow_log_id" {
description = "ID of the VPC flow log."
value = aws_flow_log.this.id
}

output "flow_log_group_arn" {
description = "ARN of the CloudWatch log group for VPC flow logs."
value = aws_cloudwatch_log_group.flow_log.arn
}
54 changes: 54 additions & 0 deletions modules/aws/vpc/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
variable "name" {
description = "Name prefix for all VPC resources."
type = string
}

variable "cidr_block" {
description = "CIDR block for the VPC (e.g. 10.10.0.0/16)."
type = string
}

variable "availability_zones" {
description = "List of availability zones for subnet placement."
type = list(string)
}

variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets, one per availability zone."
type = list(string)
}

variable "private_subnet_cidrs" {
description = "CIDR blocks for private subnets, one per availability zone."
type = list(string)
}

variable "map_public_ip_on_launch" {
description = "Auto-assign public IPv4 addresses to instances launched in public subnets."
type = bool
default = false
}

variable "enable_nat_gateway" {
description = "Create a NAT gateway for private subnet internet access."
type = bool
default = false
}

variable "single_nat_gateway" {
description = "Use a single NAT gateway instead of one per AZ. Only applies when enable_nat_gateway is true."
type = bool
default = true
}

variable "flow_log_traffic_type" {
description = "Type of traffic to capture in VPC flow logs (ACCEPT, REJECT, or ALL)."
type = string
default = "ALL"
}

variable "flow_log_retention_days" {
description = "Number of days to retain VPC flow logs in CloudWatch."
type = number
default = 30
}
18 changes: 18 additions & 0 deletions workspaces/nonprod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,21 @@ module "github_oidc" {
role_name = "github_oidc_role"
allowed_repos = var.allowed_github_repos
}

module "vpc" {
source = "../../modules/aws/vpc"

name = "durianpy-nonprod"
cidr_block = "10.30.0.0/16"
availability_zones = ["${var.aws_region}a", "${var.aws_region}b", "${var.aws_region}c"]
public_subnet_cidrs = [
"10.30.0.0/20",
"10.30.16.0/20",
"10.30.32.0/20",
]
private_subnet_cidrs = [
"10.30.48.0/20",
"10.30.64.0/20",
"10.30.80.0/20",
]
}
15 changes: 15 additions & 0 deletions workspaces/nonprod/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,18 @@ output "budget_name" {
description = "Name of the nonprod account cost budget."
value = module.budget.budget_name
}

output "vpc_id" {
description = "ID of the nonprod account VPC."
value = module.vpc.vpc_id
}

output "vpc_public_subnet_ids" {
description = "Public subnet IDs in the nonprod account VPC."
value = module.vpc.public_subnet_ids
}

output "vpc_private_subnet_ids" {
description = "Private subnet IDs in the nonprod account VPC."
value = module.vpc.private_subnet_ids
}
18 changes: 18 additions & 0 deletions workspaces/prod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,21 @@ module "github_oidc" {
# enabled_apis = var.gcp_enabled_apis
# iam_bindings = var.gcp_iam_bindings
# }

module "vpc" {
source = "../../modules/aws/vpc"

name = "durianpy-prod"
cidr_block = "10.20.0.0/16"
availability_zones = ["${var.aws_region}a", "${var.aws_region}b", "${var.aws_region}c"]
public_subnet_cidrs = [
"10.20.0.0/20",
"10.20.16.0/20",
"10.20.32.0/20",
]
private_subnet_cidrs = [
"10.20.48.0/20",
"10.20.64.0/20",
"10.20.80.0/20",
]
}
Loading
Loading