1
0

initial commit

This commit is contained in:
xpk
2026-02-13 15:44:24 +08:00
parent 66be8224f4
commit 09ce4c881a
570 changed files with 61807 additions and 0 deletions
@@ -0,0 +1,109 @@
# Overview
This module performs the following tasks:
- Create VPC, vpcflow log
- Create subnets in every AZ
- Create IGW, NGW
- Create s3 and ddb endpoints which are free
- Additional CIDR, if any, will introduce a 10s wait before subnet creation
## Subnet addressing
Subnet cidrs needs to be specified manually
## Inputs:
| Name | Description | Type | Default | Required |
|---------------------------------|---------------------------------------------------|---------------|---------|----------|
| private-subnet-cidrs | private subnets | list | [] | yes |
| public-subnet-cidrs | public subnets | list | [] | yes |
| create-nat-gateway | whether to deploy NAT gateway for private subnets | bool | true | yes |
| vpc-cidr | VPC cidr | string | none | yes |
| enable-flowlog | whether to enable vpc flowlog | bool | true | yes |
| vpcflowlog-retain-days | number of days to retain vpc cloudwatch log | number | 90 | yes |
| vpcflowlog-cwl-loggroup-key-arn | kms key alias arn for log group encryption | string | none | yes |
| secondary_cidr_blocks | Additional CIDR blocks to be associated with VPC | list(string) | none | no |
| resource-prefix | Prefix of resource name | string | "" | yes |
## Outputs:
| Name | Description | Type |
|-----------------------|-------------------------|---------|
| vpc_id | vpc id | string |
| public_subnets | list of cidr blocks | list |
| private_subnets | list of cidr blocks | list |
| secondary_cidr_blocks | list of secondary cidrs | list |
## Using s3 bucket for flowlog
Make sure the bucket policy allows access from delivery.logs. If the bucket is encrypted with CMK,
make sure the key policy allows the aws service delivery.logs.amazonaws.com.
### Sample s3 bucket policy
```json
{
"Id" : "policy01",
"Version" : "2012-10-17",
"Statement" : [
{
"Sid" : "AWSLogDeliveryWrite",
"Effect" : "Allow",
"Principal" : {
"Service" : "delivery.logs.amazonaws.com"
},
"Action" : "s3:PutObject",
"Resource" : "arn:aws:s3:::BUCKET_NAME/*"
},
{
"Sid" : "AWSLogDeliveryCheck",
"Effect" : "Allow",
"Principal" : {
"Service" : "delivery.logs.amazonaws.com"
},
"Action" : "s3:GetBucketAcl",
"Resource" : "arn:aws:s3:::BUCKET_NAME"
}
]
}
```
### Sample CMK policy
```json
{
"Sid": "Allow AWS Service to use the key",
"Effect": "Allow",
"Principal": {
"Service": [
"delivery.logs.amazonaws.com",
"cloudtrail.amazonaws.com",
"s3.amazonaws.com"
]
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
```
## Example:
```hcl
module "vpc-subnets" {
source = "../../modules/networking/vpc-subnet-manual"
resource-prefix = local.resource-prefix
private-subnet-cidrs = ["172.17.0.0/24", "172.17.1.0/24"]
public-subnet-cidrs = ["172.17.10.0/24", "172.17.11.0/24"]
vpc-cidr = "172.17.0.0/16"
enable-flow-log = false
vpcflowlog-cwl-loggroup-key-arn = ""
create-nat-gateway = true
create-free-vpc-endpoints = true
}
```
@@ -0,0 +1,179 @@
data "aws_caller_identity" "this" {}
data "aws_availability_zones" "available-az" {
state = "available"
}
data "aws_default_tags" "this" {
lifecycle {
postcondition {
condition = length(self.tags) >= 1
error_message = "Validation failed: Provider default_tags not set."
}
}
}
locals {
no-az = 2 # hard-coding to 2AZ
vpc-cidr = var.vpc-cidr
}
resource "aws_subnet" "private-subnets" {
count = length(var.private-subnet-cidrs)
vpc_id = aws_vpc.vpc.id
availability_zone = element(data.aws_availability_zones.available-az.names, count.index % 2)
cidr_block = var.private-subnet-cidrs[count.index]
tags = merge(data.aws_default_tags.this.tags, {
Name = "${var.resource-prefix}-private-${split("-", element(data.aws_availability_zones.available-az.names, count.index))[2]}-${count.index + 1}"
TfInternal = try(time_sleep.wait-10s[0].triggers.slept, "na")
})
}
resource "aws_subnet" "public-subnets" {
count = length(var.public-subnet-cidrs)
vpc_id = aws_vpc.vpc.id
availability_zone = element(data.aws_availability_zones.available-az.names, count.index % 2)
cidr_block = var.public-subnet-cidrs[count.index]
tags = merge(data.aws_default_tags.this.tags, {
Name = "${var.resource-prefix}-public-${split("-", element(data.aws_availability_zones.available-az.names, count.index))[2]}-${count.index + 1}"
TfInternal = try(time_sleep.wait-10s[0].triggers.slept, "na")
})
}
resource "aws_vpc" "vpc" {
cidr_block = var.vpc-cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.resource-prefix}-vpc"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_vpc_ipv4_cidr_block_association" "additional_cidr" {
for_each = toset(var.secondary_cidr_blocks)
vpc_id = aws_vpc.vpc.id
cidr_block = each.value
}
# Optionally wait for additional cidr association
resource "time_sleep" "wait-10s" {
depends_on = [aws_vpc_ipv4_cidr_block_association.additional_cidr]
count = length(var.secondary_cidr_blocks)
create_duration = "10s"
triggers = {
slept = length(aws_vpc_ipv4_cidr_block_association.additional_cidr)
}
}
resource "aws_internet_gateway" "igw" {
count = length(var.public-subnet-cidrs) > 0 ? 1 : 0
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.resource-prefix}-igw"
}
}
resource "aws_eip" "ngw-eip" {
count = var.create-nat-gateway ? 1 : 0
# deprecated # vpc = true
domain = "vpc"
depends_on = [aws_internet_gateway.igw]
}
resource "aws_nat_gateway" "ngw" {
count = var.create-nat-gateway ? 1 : 0
allocation_id = aws_eip.ngw-eip[0].id
subnet_id = aws_subnet.public-subnets[0].id
tags = {
Name = "${var.resource-prefix}-ngw"
}
depends_on = [aws_internet_gateway.igw]
}
resource "aws_route_table" "public-route-table" {
count = length(var.public-subnet-cidrs) > 0 ? 1 : 0
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.resource-prefix}-publicroutetable"
}
}
resource "aws_route_table" "private-route-table" {
count = length(var.private-subnet-cidrs) > 0 ? 1 : 0
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.resource-prefix}-privateroutetable"
}
}
resource "aws_route" "public-routes" {
count = length(var.public-subnet-cidrs) > 0 ? 1 : 0
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw[0].id
route_table_id = aws_route_table.public-route-table[0].id
}
resource "aws_route" "private-routes" {
count = length(var.private-subnet-cidrs) > 0 && var.create-nat-gateway ? 1 : 0
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.ngw[0].id
route_table_id = aws_route_table.private-route-table[0].id
}
resource "aws_route_table_association" "public_route_association" {
count = length(aws_subnet.public-subnets)
route_table_id = aws_route_table.public-route-table[0].id
subnet_id = aws_subnet.public-subnets[count.index].id
}
resource "aws_route_table_association" "private_route_association" {
count = length(aws_subnet.private-subnets)
route_table_id = aws_route_table.private-route-table[0].id
subnet_id = aws_subnet.private-subnets[count.index].id
}
/*
harden default security group. the default sg created by aws allows all egress.
this resource limits ingress and egress from and to itself
*/
resource "aws_default_security_group" "default-sg" {
vpc_id = aws_vpc.vpc.id
ingress {
protocol = -1
self = true
from_port = 0
to_port = 0
description = "Allow traffic coming from this SG"
}
egress {
from_port = 0
protocol = -1
to_port = 0
self = true
description = "Allow traffic going to this SG"
}
tags = {
Name = "${var.resource-prefix}-defaultsg"
}
}
# Enable gateway endpoints which are free
module "vpc-ep" {
count = var.create-free-vpc-endpoints ? 1 : 0
source = "../vpc-endpoints"
gateway-ep-services = ["s3", "dynamodb"]
interface-ep-services = []
resource-prefix = var.resource-prefix
vpc-id = aws_vpc.vpc.id
}
@@ -0,0 +1,39 @@
output "vpc_id" {
value = aws_vpc.vpc.id
}
output "vpc-cidr" {
value = aws_vpc.vpc.cidr_block
}
output "public_subnets" {
value = aws_subnet.public-subnets.*.cidr_block
}
output "private_subnets" {
value = aws_subnet.private-subnets.*.cidr_block
}
output "public-subnet-ids" {
value = aws_subnet.public-subnets.*.id
}
output "private-subnet-ids" {
value = aws_subnet.private-subnets.*.id
}
output "private-route-table-id" {
value = aws_route_table.private-route-table.*.id
}
output "public-route-table-id" {
value = aws_route_table.public-route-table.*.id
}
output "route_tables_for_gateway_endpoints" {
value = concat(aws_route_table.public-route-table.*.id, aws_route_table.private-route-table.*.id)
}
output "secondary_cidr_blocks" {
value = var.secondary_cidr_blocks
}
@@ -0,0 +1,13 @@
# requires 1.3.0 for postcondition validation
# https://learn.hashicorp.com/tutorials/terraform/custom-conditions
# provider 5.0.0 or above is required by the domain attribute in aws_eip
terraform {
required_version = "~> 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0.0"
}
}
}
@@ -0,0 +1,65 @@
variable "resource-prefix" {
type = string
description = "Prefix of resource"
}
# VPC variables
variable "vpc-cidr" {
type = string
description = "VPC primary CIDR"
}
variable "private-subnet-cidrs" {
type = list(string)
description = "Private subnet CIDRs"
}
variable "public-subnet-cidrs" {
type = list(string)
description = "Public subnet CIDRs"
default = null
}
variable "create-nat-gateway" {
type = bool
default = false
}
variable "flow-log-destination" {
type = string
description = "Destination of flowlog. Valid destinations are s3 or cwlog"
default = null
}
variable "flow-log-bucket-arn" {
type = string
default = null
description = "Arn of S3 bucket to be used for flow logging"
}
variable "enable-flow-log" {
type = bool
default = true
}
variable "vpcflowlog-retain-days" {
type = number
default = 90
description = "Log retention period for CWlogs"
}
variable "vpcflowlog-cwl-loggroup-key-arn" {
type = string
description = "KMS key arn for cwlog encryption"
}
variable "create-free-vpc-endpoints" {
type = bool
default = true
}
variable "secondary_cidr_blocks" {
type = list(string)
description = "Additional cidr blocks"
default = []
}
@@ -0,0 +1,81 @@
resource "aws_flow_log" "vpc-flowlog" {
count = var.enable-flow-log && var.flow-log-destination == "cwlog" ? 1 : 0
iam_role_arn = aws_iam_role.vpcflowlog-role[0].arn
log_destination = aws_cloudwatch_log_group.vpcflowlog-loggroup[0].arn
traffic_type = "ALL"
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.resource-prefix}-vpcflowlog"
}
}
resource "aws_flow_log" "vpc-flowlog-s3" {
count = var.enable-flow-log && var.flow-log-destination == "s3" ? 1 : 0
log_destination_type = "s3"
log_destination = var.flow-log-bucket-arn
traffic_type = "ALL"
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.resource-prefix}-vpcflowlog"
}
}
resource "aws_cloudwatch_log_group" "vpcflowlog-loggroup" {
count = var.enable-flow-log && var.flow-log-destination == "cwlog" ? 1 : 0
name_prefix = "vpcflowlog/${aws_vpc.vpc.id}/"
kms_key_id = var.vpcflowlog-cwl-loggroup-key-arn
retention_in_days = var.vpcflowlog-retain-days
}
resource "random_id" "rid" {
byte_length = 2
}
resource "aws_iam_role" "vpcflowlog-role" {
count = var.enable-flow-log && var.flow-log-destination == "cwlog" ? 1 : 0
name = "VpcFlowlogRole-${random_id.rid.dec}"
path = "/service/"
assume_role_policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Sid" : "",
"Effect" : "Allow",
"Principal" : {
"Service" : "vpc-flow-logs.amazonaws.com"
},
"Action" : "sts:AssumeRole"
}
]
}
)
}
resource "aws_iam_role_policy" "vpcflowlog-role-policy" {
count = var.enable-flow-log && var.flow-log-destination == "cwlog" ? 1 : 0
name = "VpcFlowlogRole-${random_id.rid.dec}"
role = aws_iam_role.vpcflowlog-role[0].id
policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Action" : [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"kms:Encrypt",
"kms:ReEncrypt",
"kms:Decrypt"
],
"Effect" : "Allow",
"Resource" : "*"
}
]
}
)
}