Commit 2c034240 authored by Alex Bryant's avatar Alex Bryant Committed by GitHub

feat: Add outpost support (subnet, NACL, IPv6) (#542)

parent 0c2db005
...@@ -7,7 +7,7 @@ on: ...@@ -7,7 +7,7 @@ on:
- master - master
jobs: jobs:
# Min Terraform version(s) # Min Terraform version(s)
getDirectories: getDirectories:
name: Get root directories name: Get root directories
runs-on: ubuntu-latest runs-on: ubuntu-latest
...@@ -59,7 +59,7 @@ jobs: ...@@ -59,7 +59,7 @@ jobs:
pre-commit run terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf) pre-commit run terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf)
# Max Terraform version # Max Terraform version
getBaseVersion: getBaseVersion:
name: Module max TF version name: Module max TF version
runs-on: ubuntu-latest runs-on: ubuntu-latest
...@@ -94,7 +94,7 @@ jobs: ...@@ -94,7 +94,7 @@ jobs:
- name: Install pre-commit dependencies - name: Install pre-commit dependencies
run: | run: |
pip install pre-commit pip install pre-commit
curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest | grep -o -E "https://.+?-v0.12.0-linux-amd64" | head -n1)" > terraform-docs && chmod +x terraform-docs && sudo mv terraform-docs /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest | grep -o -E "https://.+?-v0.12\..+?-linux-amd64" | head -n1)" > terraform-docs && chmod +x terraform-docs && sudo mv terraform-docs /usr/bin/
curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/
- name: Execute pre-commit - name: Execute pre-commit
# Run all pre-commit checks on max version supported # Run all pre-commit checks on max version supported
......
This diff is collapsed.
# VPC with Outpost Subnet
Configuration in this directory creates a VPC with public, private, and private outpost subnets.
This configuration uses data-source to find an available Outpost by name. Change it according to your needs in order to run this example.
[Read more about AWS regions, availability zones and local zones](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions-availability-zones).
## Usage
To run this example you need to execute:
```bash
$ terraform init
$ terraform plan
$ terraform apply
```
Note that this example may create resources which can cost money (AWS Elastic IP, for example). Run `terraform destroy` when you don't need these resources.
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.12.21 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.5.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.5.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
| <a name="module_vpc"></a> [vpc](#module\_vpc) | ../../ | |
## Resources
| Name | Type |
|------|------|
| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
| [aws_outposts_outpost.shared](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/outposts_outpost) | data source |
## Inputs
No inputs.
## Outputs
| Name | Description |
|------|-------------|
| <a name="output_azs"></a> [azs](#output\_azs) | A list of availability zones specified as argument to this module |
| <a name="output_nat_public_ips"></a> [nat\_public\_ips](#output\_nat\_public\_ips) | List of public Elastic IPs created for AWS NAT Gateway |
| <a name="output_outpost_subnets"></a> [outpost\_subnets](#output\_outpost\_subnets) | List of IDs of private subnets |
| <a name="output_private_subnets"></a> [private\_subnets](#output\_private\_subnets) | List of IDs of private subnets |
| <a name="output_public_subnets"></a> [public\_subnets](#output\_public\_subnets) | List of IDs of public subnets |
| <a name="output_vpc_cidr_block"></a> [vpc\_cidr\_block](#output\_vpc\_cidr\_block) | The CIDR block of the VPC |
| <a name="output_vpc_id"></a> [vpc\_id](#output\_vpc\_id) | The ID of the VPC |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
provider "aws" {
region = "us-west-2"
assume_role {
role_arn = "arn:aws:iam::562806027032:role/outpost-shared-anton"
}
}
data "aws_outposts_outpost" "shared" {
name = "SEA19.07"
}
data "aws_availability_zones" "available" {}
module "vpc" {
source = "../../"
name = "outpost-example"
cidr = "10.0.0.0/16"
azs = [
data.aws_availability_zones.available.names[0],
data.aws_availability_zones.available.names[1],
data.aws_availability_zones.available.names[2],
]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
# Outpost is using single AZ specified in `outpost_az`
outpost_subnets = ["10.0.50.0/24", "10.0.51.0/24"]
outpost_arn = data.aws_outposts_outpost.shared.arn
outpost_az = data.aws_outposts_outpost.shared.availability_zone
# IPv6
enable_ipv6 = true
outpost_subnet_assign_ipv6_address_on_creation = true
outpost_subnet_ipv6_prefixes = [2, 3, 4]
# NAT Gateway
enable_nat_gateway = true
single_nat_gateway = true
# Network ACLs
outpost_dedicated_network_acl = true
outpost_inbound_acl_rules = local.network_acls["outpost_inbound"]
outpost_outbound_acl_rules = local.network_acls["outpost_outbound"]
tags = {
Owner = "user"
Environment = "dev"
}
}
locals {
network_acls = {
outpost_inbound = [
{
rule_number = 100
rule_action = "allow"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_block = "0.0.0.0/0"
},
{
rule_number = 110
rule_action = "allow"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_block = "0.0.0.0/0"
},
{
rule_number = 120
rule_action = "allow"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_block = "0.0.0.0/0"
},
{
rule_number = 130
rule_action = "allow"
from_port = 3389
to_port = 3389
protocol = "tcp"
cidr_block = "0.0.0.0/0"
},
{
rule_number = 140
rule_action = "allow"
from_port = 80
to_port = 80
protocol = "tcp"
ipv6_cidr_block = "::/0"
},
]
outpost_outbound = [
{
rule_number = 100
rule_action = "allow"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_block = "0.0.0.0/0"
},
{
rule_number = 110
rule_action = "allow"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_block = "0.0.0.0/0"
},
{
rule_number = 120
rule_action = "allow"
from_port = 1433
to_port = 1433
protocol = "tcp"
cidr_block = "10.0.100.0/22"
},
{
rule_number = 130
rule_action = "allow"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_block = "10.0.100.0/22"
},
{
rule_number = 140
rule_action = "allow"
icmp_code = -1
icmp_type = 8
protocol = "icmp"
cidr_block = "10.0.0.0/22"
},
{
rule_number = 150
rule_action = "allow"
from_port = 90
to_port = 90
protocol = "tcp"
ipv6_cidr_block = "::/0"
},
]
}
}
# VPC
output "vpc_id" {
description = "The ID of the VPC"
value = module.vpc.vpc_id
}
# CIDR blocks
output "vpc_cidr_block" {
description = "The CIDR block of the VPC"
value = module.vpc.vpc_cidr_block
}
# Subnets
output "private_subnets" {
description = "List of IDs of private subnets"
value = module.vpc.private_subnets
}
output "public_subnets" {
description = "List of IDs of public subnets"
value = module.vpc.public_subnets
}
output "outpost_subnets" {
description = "List of IDs of private subnets"
value = module.vpc.outpost_subnets
}
# NAT gateways
output "nat_public_ips" {
description = "List of public Elastic IPs created for AWS NAT Gateway"
value = module.vpc.nat_public_ips
}
# AZs
output "azs" {
description = "A list of availability zones specified as argument to this module"
value = module.vpc.azs
}
terraform {
required_version = ">= 0.12.21"
required_providers {
aws = ">= 3.5.0"
}
}
...@@ -419,6 +419,34 @@ resource "aws_subnet" "private" { ...@@ -419,6 +419,34 @@ resource "aws_subnet" "private" {
) )
} }
#################
# Outpost subnet
#################
resource "aws_subnet" "outpost" {
count = var.create_vpc && length(var.outpost_subnets) > 0 ? length(var.outpost_subnets) : 0
vpc_id = local.vpc_id
cidr_block = var.outpost_subnets[count.index]
availability_zone = var.outpost_az
assign_ipv6_address_on_creation = var.outpost_subnet_assign_ipv6_address_on_creation == null ? var.assign_ipv6_address_on_creation : var.outpost_subnet_assign_ipv6_address_on_creation
ipv6_cidr_block = var.enable_ipv6 && length(var.outpost_subnet_ipv6_prefixes) > 0 ? cidrsubnet(aws_vpc.this[0].ipv6_cidr_block, 8, var.outpost_subnet_ipv6_prefixes[count.index]) : null
outpost_arn = var.outpost_arn
tags = merge(
{
"Name" = format(
"%s-${var.outpost_subnet_suffix}-%s",
var.name,
var.outpost_az,
)
},
var.tags,
var.outpost_subnet_tags,
)
}
################## ##################
# Database subnet # Database subnet
################## ##################
...@@ -585,6 +613,7 @@ resource "aws_default_network_acl" "this" { ...@@ -585,6 +613,7 @@ resource "aws_default_network_acl" "this" {
aws_subnet.database.*.id, aws_subnet.database.*.id,
aws_subnet.redshift.*.id, aws_subnet.redshift.*.id,
aws_subnet.elasticache.*.id, aws_subnet.elasticache.*.id,
aws_subnet.outpost.*.id,
])), ])),
compact(flatten([ compact(flatten([
aws_network_acl.public.*.subnet_ids, aws_network_acl.public.*.subnet_ids,
...@@ -593,6 +622,7 @@ resource "aws_default_network_acl" "this" { ...@@ -593,6 +622,7 @@ resource "aws_default_network_acl" "this" {
aws_network_acl.database.*.subnet_ids, aws_network_acl.database.*.subnet_ids,
aws_network_acl.redshift.*.subnet_ids, aws_network_acl.redshift.*.subnet_ids,
aws_network_acl.elasticache.*.subnet_ids, aws_network_acl.elasticache.*.subnet_ids,
aws_network_acl.outpost.*.subnet_ids,
])) ]))
) )
...@@ -738,6 +768,58 @@ resource "aws_network_acl_rule" "private_outbound" { ...@@ -738,6 +768,58 @@ resource "aws_network_acl_rule" "private_outbound" {
ipv6_cidr_block = lookup(var.private_outbound_acl_rules[count.index], "ipv6_cidr_block", null) ipv6_cidr_block = lookup(var.private_outbound_acl_rules[count.index], "ipv6_cidr_block", null)
} }
#######################
# Outpost Network ACLs
#######################
resource "aws_network_acl" "outpost" {
count = var.create_vpc && var.outpost_dedicated_network_acl && length(var.outpost_subnets) > 0 ? 1 : 0
vpc_id = element(concat(aws_vpc.this.*.id, [""]), 0)
subnet_ids = aws_subnet.outpost.*.id
tags = merge(
{
"Name" = format("%s-${var.outpost_subnet_suffix}", var.name)
},
var.tags,
var.outpost_acl_tags,
)
}
resource "aws_network_acl_rule" "outpost_inbound" {
count = var.create_vpc && var.outpost_dedicated_network_acl && length(var.outpost_subnets) > 0 ? length(var.outpost_inbound_acl_rules) : 0
network_acl_id = aws_network_acl.outpost[0].id
egress = false
rule_number = var.outpost_inbound_acl_rules[count.index]["rule_number"]
rule_action = var.outpost_inbound_acl_rules[count.index]["rule_action"]
from_port = lookup(var.outpost_inbound_acl_rules[count.index], "from_port", null)
to_port = lookup(var.outpost_inbound_acl_rules[count.index], "to_port", null)
icmp_code = lookup(var.outpost_inbound_acl_rules[count.index], "icmp_code", null)
icmp_type = lookup(var.outpost_inbound_acl_rules[count.index], "icmp_type", null)
protocol = var.outpost_inbound_acl_rules[count.index]["protocol"]
cidr_block = lookup(var.outpost_inbound_acl_rules[count.index], "cidr_block", null)
ipv6_cidr_block = lookup(var.outpost_inbound_acl_rules[count.index], "ipv6_cidr_block", null)
}
resource "aws_network_acl_rule" "outpost_outbound" {
count = var.create_vpc && var.outpost_dedicated_network_acl && length(var.outpost_subnets) > 0 ? length(var.outpost_outbound_acl_rules) : 0
network_acl_id = aws_network_acl.outpost[0].id
egress = true
rule_number = var.outpost_outbound_acl_rules[count.index]["rule_number"]
rule_action = var.outpost_outbound_acl_rules[count.index]["rule_action"]
from_port = lookup(var.outpost_outbound_acl_rules[count.index], "from_port", null)
to_port = lookup(var.outpost_outbound_acl_rules[count.index], "to_port", null)
icmp_code = lookup(var.outpost_outbound_acl_rules[count.index], "icmp_code", null)
icmp_type = lookup(var.outpost_outbound_acl_rules[count.index], "icmp_type", null)
protocol = var.outpost_outbound_acl_rules[count.index]["protocol"]
cidr_block = lookup(var.outpost_outbound_acl_rules[count.index], "cidr_block", null)
ipv6_cidr_block = lookup(var.outpost_outbound_acl_rules[count.index], "ipv6_cidr_block", null)
}
######################## ########################
# Intra Network ACLs # Intra Network ACLs
######################## ########################
...@@ -1042,6 +1124,16 @@ resource "aws_route_table_association" "private" { ...@@ -1042,6 +1124,16 @@ resource "aws_route_table_association" "private" {
) )
} }
resource "aws_route_table_association" "outpost" {
count = var.create_vpc && length(var.outpost_subnets) > 0 ? length(var.outpost_subnets) : 0
subnet_id = element(aws_subnet.outpost.*.id, count.index)
route_table_id = element(
aws_route_table.private.*.id,
var.single_nat_gateway ? 0 : count.index,
)
}
resource "aws_route_table_association" "database" { resource "aws_route_table_association" "database" {
count = var.create_vpc && length(var.database_subnets) > 0 ? length(var.database_subnets) : 0 count = var.create_vpc && length(var.database_subnets) > 0 ? length(var.database_subnets) : 0
...@@ -1201,3 +1293,4 @@ resource "aws_default_vpc" "this" { ...@@ -1201,3 +1293,4 @@ resource "aws_default_vpc" "this" {
var.default_vpc_tags, var.default_vpc_tags,
) )
} }
...@@ -108,6 +108,26 @@ output "public_subnets_ipv6_cidr_blocks" { ...@@ -108,6 +108,26 @@ output "public_subnets_ipv6_cidr_blocks" {
value = aws_subnet.public.*.ipv6_cidr_block value = aws_subnet.public.*.ipv6_cidr_block
} }
output "outpost_subnets" {
description = "List of IDs of outpost subnets"
value = aws_subnet.outpost.*.id
}
output "outpost_subnet_arns" {
description = "List of ARNs of outpost subnets"
value = aws_subnet.outpost.*.arn
}
output "outpost_subnets_cidr_blocks" {
description = "List of cidr_blocks of outpost subnets"
value = aws_subnet.outpost.*.cidr_block
}
output "outpost_subnets_ipv6_cidr_blocks" {
description = "List of IPv6 cidr_blocks of outpost subnets in an IPv6 enabled VPC"
value = aws_subnet.outpost.*.ipv6_cidr_block
}
output "database_subnets" { output "database_subnets" {
description = "List of IDs of database subnets" description = "List of IDs of database subnets"
value = aws_subnet.database.*.id value = aws_subnet.database.*.id
...@@ -442,6 +462,16 @@ output "private_network_acl_arn" { ...@@ -442,6 +462,16 @@ output "private_network_acl_arn" {
value = concat(aws_network_acl.private.*.arn, [""])[0] value = concat(aws_network_acl.private.*.arn, [""])[0]
} }
output "outpost_network_acl_id" {
description = "ID of the outpost network ACL"
value = concat(aws_network_acl.outpost.*.id, [""])[0]
}
output "outpost_network_acl_arn" {
description = "ARN of the outpost network ACL"
value = concat(aws_network_acl.outpost.*.arn, [""])[0]
}
output "intra_network_acl_id" { output "intra_network_acl_id" {
description = "ID of the intra network ACL" description = "ID of the intra network ACL"
value = concat(aws_network_acl.intra.*.id, [""])[0] value = concat(aws_network_acl.intra.*.id, [""])[0]
......
...@@ -34,6 +34,12 @@ variable "public_subnet_ipv6_prefixes" { ...@@ -34,6 +34,12 @@ variable "public_subnet_ipv6_prefixes" {
default = [] default = []
} }
variable "outpost_subnet_ipv6_prefixes" {
description = "Assigns IPv6 outpost subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list"
type = list(string)
default = []
}
variable "database_subnet_ipv6_prefixes" { variable "database_subnet_ipv6_prefixes" {
description = "Assigns IPv6 database subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list" description = "Assigns IPv6 database subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list"
type = list(string) type = list(string)
...@@ -76,6 +82,12 @@ variable "public_subnet_assign_ipv6_address_on_creation" { ...@@ -76,6 +82,12 @@ variable "public_subnet_assign_ipv6_address_on_creation" {
default = null default = null
} }
variable "outpost_subnet_assign_ipv6_address_on_creation" {
description = "Assign IPv6 address on outpost subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map_public_ip_on_launch"
type = bool
default = null
}
variable "database_subnet_assign_ipv6_address_on_creation" { variable "database_subnet_assign_ipv6_address_on_creation" {
description = "Assign IPv6 address on database subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map_public_ip_on_launch" description = "Assign IPv6 address on database subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map_public_ip_on_launch"
type = bool type = bool
...@@ -124,6 +136,12 @@ variable "private_subnet_suffix" { ...@@ -124,6 +136,12 @@ variable "private_subnet_suffix" {
default = "private" default = "private"
} }
variable "outpost_subnet_suffix" {
description = "Suffix to append to outpost subnets name"
type = string
default = "outpost"
}
variable "intra_subnet_suffix" { variable "intra_subnet_suffix" {
description = "Suffix to append to intra subnets name" description = "Suffix to append to intra subnets name"
type = string type = string
...@@ -160,6 +178,12 @@ variable "private_subnets" { ...@@ -160,6 +178,12 @@ variable "private_subnets" {
default = [] default = []
} }
variable "outpost_subnets" {
description = "A list of outpost subnets inside the VPC"
type = list(string)
default = []
}
variable "database_subnets" { variable "database_subnets" {
description = "A list of database subnets" description = "A list of database subnets"
type = list(string) type = list(string)
...@@ -2267,6 +2291,12 @@ variable "private_subnet_tags" { ...@@ -2267,6 +2291,12 @@ variable "private_subnet_tags" {
default = {} default = {}
} }
variable "outpost_subnet_tags" {
description = "Additional tags for the outpost subnets"
type = map(string)
default = {}
}
variable "public_route_table_tags" { variable "public_route_table_tags" {
description = "Additional tags for the public route tables" description = "Additional tags for the public route tables"
type = map(string) type = map(string)
...@@ -2351,6 +2381,12 @@ variable "private_acl_tags" { ...@@ -2351,6 +2381,12 @@ variable "private_acl_tags" {
default = {} default = {}
} }
variable "outpost_acl_tags" {
description = "Additional tags for the outpost subnets network ACL"
type = map(string)
default = {}
}
variable "intra_acl_tags" { variable "intra_acl_tags" {
description = "Additional tags for the intra subnets network ACL" description = "Additional tags for the intra subnets network ACL"
type = map(string) type = map(string)
...@@ -2525,6 +2561,12 @@ variable "private_dedicated_network_acl" { ...@@ -2525,6 +2561,12 @@ variable "private_dedicated_network_acl" {
default = false default = false
} }
variable "outpost_dedicated_network_acl" {
description = "Whether to use dedicated network ACL (not default) and custom rules for outpost subnets"
type = bool
default = false
}
variable "intra_dedicated_network_acl" { variable "intra_dedicated_network_acl" {
description = "Whether to use dedicated network ACL (not default) and custom rules for intra subnets" description = "Whether to use dedicated network ACL (not default) and custom rules for intra subnets"
type = bool type = bool
...@@ -2661,6 +2703,38 @@ variable "private_outbound_acl_rules" { ...@@ -2661,6 +2703,38 @@ variable "private_outbound_acl_rules" {
] ]
} }
variable "outpost_inbound_acl_rules" {
description = "Outpost subnets inbound network ACLs"
type = list(map(string))
default = [
{
rule_number = 100
rule_action = "allow"
from_port = 0
to_port = 0
protocol = "-1"
cidr_block = "0.0.0.0/0"
},
]
}
variable "outpost_outbound_acl_rules" {
description = "Outpost subnets outbound network ACLs"
type = list(map(string))
default = [
{
rule_number = 100
rule_action = "allow"
from_port = 0
to_port = 0
protocol = "-1"
cidr_block = "0.0.0.0/0"
},
]
}
variable "intra_inbound_acl_rules" { variable "intra_inbound_acl_rules" {
description = "Intra subnets inbound network ACLs" description = "Intra subnets inbound network ACLs"
type = list(map(string)) type = list(map(string))
...@@ -2902,3 +2976,15 @@ variable "create_egress_only_igw" { ...@@ -2902,3 +2976,15 @@ variable "create_egress_only_igw" {
type = bool type = bool
default = true default = true
} }
variable "outpost_arn" {
description = "ARN of Outpost you want to create a subnet in."
type = string
default = null
}
variable "outpost_az" {
description = "AZ where Outpost is anchored."
type = string
default = null
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment