From f9a4bca65587dec585edfb1270ddaabe3d4ac1bb Mon Sep 17 00:00:00 2001 From: xpk Date: Tue, 22 Oct 2024 14:35:07 +0800 Subject: [PATCH] UPD: Updated module in preparation for multiaz support for NGW --- modules/networking/VpcSubnet/README.md | 95 ++++++++++ modules/networking/VpcSubnet/example/main.tf | 12 ++ modules/networking/VpcSubnet/main.tf | 177 ++++++++++++++++++ .../VpcSubnet/modules/RouteTables/README.md | 39 ++++ .../VpcSubnet/modules/RouteTables/main.tf | 17 ++ .../modules/RouteTables/variables.tf | 19 ++ modules/networking/VpcSubnet/outputs.tf | 39 ++++ modules/networking/VpcSubnet/variables.tf | 66 +++++++ modules/networking/VpcSubnet/versions.tf | 9 + modules/networking/VpcSubnet/vpc-flowlog.tf | 81 ++++++++ 10 files changed, 554 insertions(+) create mode 100644 modules/networking/VpcSubnet/README.md create mode 100644 modules/networking/VpcSubnet/example/main.tf create mode 100644 modules/networking/VpcSubnet/main.tf create mode 100644 modules/networking/VpcSubnet/modules/RouteTables/README.md create mode 100644 modules/networking/VpcSubnet/modules/RouteTables/main.tf create mode 100644 modules/networking/VpcSubnet/modules/RouteTables/variables.tf create mode 100644 modules/networking/VpcSubnet/outputs.tf create mode 100644 modules/networking/VpcSubnet/variables.tf create mode 100644 modules/networking/VpcSubnet/versions.tf create mode 100644 modules/networking/VpcSubnet/vpc-flowlog.tf diff --git a/modules/networking/VpcSubnet/README.md b/modules/networking/VpcSubnet/README.md new file mode 100644 index 0000000..8086464 --- /dev/null +++ b/modules/networking/VpcSubnet/README.md @@ -0,0 +1,95 @@ + +# 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 + +## Limitations +- Only 1 NAT gateway is supported with this module. MultiAZ support is work in progress + +## Subnet addressing + +Subnet cidrs needs to be specified manually + +## Requirements + +| Name | Version | +|------|---------| +| terraform | >= 1.3.0 | +| aws | >= 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| aws | >= 5.0 | +| random | n/a | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| private-route | ./modules/RouteTables | n/a | +| vpc-ep | ../vpc-endpoints | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.vpcflowlog-loggroup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_default_security_group.default-sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group) | resource | +| [aws_eip.ngw-eip](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource | +| [aws_flow_log.vpc-flowlog](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log) | resource | +| [aws_flow_log.vpc-flowlog-s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log) | resource | +| [aws_iam_role.vpcflowlog-role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.vpcflowlog-role-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_internet_gateway.igw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource | +| [aws_nat_gateway.ngw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway) | resource | +| [aws_route.public-routes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route_table.public-route-table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | +| [aws_route_table_association.public_route_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | +| [aws_subnet.private-subnets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | +| [aws_subnet.public-subnets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | +| [aws_vpc.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource | +| [aws_vpc_ipv4_cidr_block_association.additional_cidr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipv4_cidr_block_association) | resource | +| [random_id.rid](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [aws_availability_zones.available-az](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_default_tags.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/default_tags) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| create-free-vpc-endpoints | n/a | `bool` | `true` | no | +| create-nat-gateway | n/a | `bool` | `false` | no | +| enable-flow-log | n/a | `bool` | `true` | no | +| flow-log-bucket-arn | Arn of S3 bucket to be used for flow logging | `string` | `null` | no | +| flow-log-destination | Destination of flowlog. Valid destinations are s3 or cwlog | `string` | `null` | no | +| private-subnet-cidrs | Private subnet CIDRs | `list(string)` | `[]` | no | +| public-subnet-cidrs | Public subnet CIDRs | `list(string)` | `[]` | no | +| resource-prefix | Prefix of resource | `string` | n/a | yes | +| secondary\_cidr\_blocks | Additional cidr blocks | `list(string)` | `[]` | no | +| vpc-cidr | VPC primary CIDR | `string` | n/a | yes | +| vpcflowlog-cwl-loggroup-key-arn | KMS key arn for cwlog encryption | `string` | n/a | yes | +| vpcflowlog-retain-days | Log retention period for CWlogs | `number` | `90` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| private-subnet-ids | n/a | +| private\_subnets | n/a | +| public-route-table-id | n/a | +| public-subnet-ids | n/a | +| public\_subnets | n/a | +| secondary\_cidr\_blocks | n/a | +| vpc-cidr | n/a | +| vpc\_id | n/a | + +--- +## Authorship +This module was developed by xpk. \ No newline at end of file diff --git a/modules/networking/VpcSubnet/example/main.tf b/modules/networking/VpcSubnet/example/main.tf new file mode 100644 index 0000000..52ea50d --- /dev/null +++ b/modules/networking/VpcSubnet/example/main.tf @@ -0,0 +1,12 @@ +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 +} \ No newline at end of file diff --git a/modules/networking/VpcSubnet/main.tf b/modules/networking/VpcSubnet/main.tf new file mode 100644 index 0000000..0a175e6 --- /dev/null +++ b/modules/networking/VpcSubnet/main.tf @@ -0,0 +1,177 @@ +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}" + }) +} + +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}" + }) +} + +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 +} + +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" + } +} + +module "private-route" { + source = "./modules/RouteTables" + count = var.create-nat-gateway ? length(aws_subnet.private-subnets) : 0 + ngw-id = aws_nat_gateway.ngw[0].id + resource-prefix = var.resource-prefix + subnet-id = aws_subnet.private-subnets[count.index].id + vpc-id = aws_vpc.vpc.id +} + +# 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 +and allow icmp only +*/ + +resource "aws_default_security_group" "default-sg" { + vpc_id = aws_vpc.vpc.id + ingress { + protocol = "icmp" + self = true + from_port = -1 + to_port = -1 + description = "Allow traffic coming from this SG" + } + egress { + from_port = -1 + protocol = "icmp" + to_port = -1 + 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 +} \ No newline at end of file diff --git a/modules/networking/VpcSubnet/modules/RouteTables/README.md b/modules/networking/VpcSubnet/modules/RouteTables/README.md new file mode 100644 index 0000000..59e41e9 --- /dev/null +++ b/modules/networking/VpcSubnet/modules/RouteTables/README.md @@ -0,0 +1,39 @@ + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| aws | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | +| [aws_route_table_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| ngw-id | NAT Gateway ID | `string` | n/a | yes | +| resource-prefix | Resource prefix | `string` | n/a | yes | +| subnet-id | Subnet ID | `string` | n/a | yes | +| vpc-id | VPC ID | `string` | n/a | yes | + +## Outputs + +No outputs. + +--- +## Authorship +This module was developed by xpk. \ No newline at end of file diff --git a/modules/networking/VpcSubnet/modules/RouteTables/main.tf b/modules/networking/VpcSubnet/modules/RouteTables/main.tf new file mode 100644 index 0000000..b798aaa --- /dev/null +++ b/modules/networking/VpcSubnet/modules/RouteTables/main.tf @@ -0,0 +1,17 @@ +resource "aws_route_table" "this" { + vpc_id = var.vpc-id + tags = { + Name = "${var.resource-prefix}-privateroutetable" + } +} + +resource "aws_route" "this" { + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = var.ngw-id + route_table_id = aws_route_table.this.id +} + +resource "aws_route_table_association" "this" { + route_table_id = aws_route_table.this.id + subnet_id = var.subnet-id +} diff --git a/modules/networking/VpcSubnet/modules/RouteTables/variables.tf b/modules/networking/VpcSubnet/modules/RouteTables/variables.tf new file mode 100644 index 0000000..3632eb8 --- /dev/null +++ b/modules/networking/VpcSubnet/modules/RouteTables/variables.tf @@ -0,0 +1,19 @@ +variable vpc-id { + type = string + description = "VPC ID" +} + +variable ngw-id { + type = string + description = "NAT Gateway ID" +} + +variable subnet-id { + type = string + description = "Subnet ID" +} + +variable resource-prefix { + type = string + description = "Resource prefix" +} diff --git a/modules/networking/VpcSubnet/outputs.tf b/modules/networking/VpcSubnet/outputs.tf new file mode 100644 index 0000000..02e26a4 --- /dev/null +++ b/modules/networking/VpcSubnet/outputs.tf @@ -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 +} \ No newline at end of file diff --git a/modules/networking/VpcSubnet/variables.tf b/modules/networking/VpcSubnet/variables.tf new file mode 100644 index 0000000..0bf673e --- /dev/null +++ b/modules/networking/VpcSubnet/variables.tf @@ -0,0 +1,66 @@ +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" + default = [] +} + +variable "public-subnet-cidrs" { + type = list(string) + description = "Public subnet CIDRs" + default = [] +} + +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 = [] +} \ No newline at end of file diff --git a/modules/networking/VpcSubnet/versions.tf b/modules/networking/VpcSubnet/versions.tf new file mode 100644 index 0000000..ceb041e --- /dev/null +++ b/modules/networking/VpcSubnet/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.3.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} \ No newline at end of file diff --git a/modules/networking/VpcSubnet/vpc-flowlog.tf b/modules/networking/VpcSubnet/vpc-flowlog.tf new file mode 100644 index 0000000..2f10904 --- /dev/null +++ b/modules/networking/VpcSubnet/vpc-flowlog.tf @@ -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" : "*" + } + ] + } + ) +}