Terraform code to create a central egress vpc using nat gateway and aws network firewall as documented by aws in Building a Scalable and Secure Multi-VPC AWS Network Infrastructure
The module creates a default firewall policy and firewall rule groups that allow output HTTP and HTTPS traffic only to a list of configurable ports.
Credit to the following authors and projects for providing helpful examples:
- aws-samples/aws-network-hub-for-terraform
- Mahattam Tomar and his project terraform-aws-network-firewall.
- Mukesh Sharma and his project terraform-aws-network-firewall-deployment-models
Truthfully, I wanted a stack as simple to deploy as Checkpoint's CloudGuard Transit Gateway Auto Scaling Group, but utilizing aws services. I work with a firewall team that has little experience with aws and a strong preference for Checkpoint and this is a demonstration of how aws services can perform the same functionality.
module "egress_vpc" {
source = "../"
name = "egress"
prefix = "fncz"
cidr = "10.1.1.0/24" # cidr shouldn't be bigger than /24
home_net = "10.0.0.0/8"
firewall_policy_arn = aws_networkfirewall_firewall_policy.egress.arn
# /24 can scale to 4 AZ
# public_subnets = ["10.1.1.64/27", "10.1.1.96/27", "10.1.1.128/27", "10.1.1.160/27"]
# firewall_subnets = ["10.1.1.0/28", "10.1.1.16/28", "10.1.1.32/28", "10.1.1.48/28"]
# transit_gateway_subnets = ["10.1.1.192/28", "10.1.1.208/28", "10.1.1.224/28", "10.1.1.240/28"]
http_ports = ["80"]
tls_ports = ["443"]
public_subnets = ["10.1.1.64/27", "10.1.1.96/27"]
firewall_subnets = ["10.1.1.0/28", "10.1.1.16/28"] # these should not be larger than /28. nothing else should live here.
transit_gateway_subnets = ["10.1.1.192/28", "10.1.1.208/28"]
availability_zone_names = ["us-east-1a", "us-east-1b"]
transit_gateway_id = "tgw-080381551298c8919"
vpc_flow_logs = "CLOUDWATCH"
tags = var.tags
firewall_policy_tags = {
Name = "default-egress-policy"
}
firewall_tags = {
vpc = "egress-vpc"
}
}
# How to add local resources and override the default egress policy
# and rules the module creates by specifying your own policy and rules
resource "aws_networkfirewall_firewall_policy" "egress" {
name = "egress-policy"
firewall_policy {
stateless_default_actions = ["aws:forward_to_sfe"]
stateless_fragment_default_actions = ["aws:forward_to_sfe"]
stateful_rule_group_reference {
resource_arn = aws_networkfirewall_rule_group.domainlist.arn
}
stateless_rule_group_reference {
priority = 10
resource_arn = aws_networkfirewall_rule_group.drop.arn
}
}
tags = merge(
{},
var.tags
)
}
resource "aws_networkfirewall_rule_group" "domainlist" {
capacity = 1000
name = "domain-white-list"
type = "STATEFUL"
rule_group {
rule_variables {
ip_sets {
key = "HOME_NET"
ip_set {
definition = ["10.0.0.0/8"]
}
}
}
rules_source {
rules_source_list {
generated_rules_type = "ALLOWLIST"
target_types = [
"HTTP_HOST",
"TLS_SNI"
]
targets = [
".amazon.com",
".amazonaws.com",
".awsstatic.com",
".aws.dev",
".auth0.com",
".google.com",
".okta.com",
"pypi.python.org",
".pypi.org",
".pythonhosted.org",
"slashdot.org"
]
}
}
}
tags = merge(
{},
var.tags
)
}
variable "tags" {
type = map(string)
default = {
costcenter = "12345"
}
}
resource "aws_networkfirewall_rule_group" "drop" {
capacity = 10
name = "egress-drop"
description = "stateless rule group that forwards http/s outbound traffic to stateful rules for inspection and drops all other traffic"
type = "STATELESS"
rule_group {
rules_source {
stateless_rules_and_custom_actions {
stateless_rule {
priority = 10
rule_definition {
actions = ["aws:forward_to_sfe"]
match_attributes {
source {
address_definition = "10.0.0.0/8"
}
source_port {
from_port = 0
to_port = 65535
}
destination {
address_definition = "0.0.0.0/0"
}
destination_port {
from_port = 80
to_port = 80
}
destination_port {
from_port = 443
to_port = 443
}
protocols = [6]
}
}
}
stateless_rule {
priority = 100
rule_definition {
actions = ["aws:drop"]
match_attributes {
source {
address_definition = "10.0.0.0/8"
}
destination {
address_definition = "0.0.0.0/0"
}
}
}
}
}
}
}
tags = merge(
{},
var.tags
)
}
# Provided as an example of accessing the anfw vpce assigned to a specific az
output "anwf-vpce-us-east-1a" {
value = lookup(module.egress_vpc.vpce_lookup, "us-east-1a")
}
output "anwf-vpce-us-east-1b" {
value = lookup(module.egress_vpc.vpce_lookup, "us-east-1b")
}
No requirements.
Name | Version |
---|---|
aws | n/a |
No modules.
Name | Description | Type | Default | Required |
---|---|---|---|---|
availability_zone_names | list of availability zones for subnet coverage, like us-east-1a, us-east-1b, etc. This module will create a set of three subnets (public, firewall, transit gateway) per availability zone | list(string) |
[ |
no |
bucket_tags | A map of tags to add to s3 buckets | map(string) |
{} |
no |
cidr | cidr block for this vpc. It really doesn't need to be larger than /24. | string |
n/a | yes |
enable_dns_hostnames | Should be true to enable DNS hostnames in the VPC | bool |
true |
no |
enable_dns_support | Should be true to enable DNS support in the VPC | bool |
true |
no |
firewall_acl_tags | Additional tags for the firewall subnets network ACL | map(string) |
{} |
no |
firewall_log_group_name | name of the cloudwatch log group for firewall logs. Defaults to "Firewall". | string |
"firewall" |
no |
firewall_policy_arn | arn of the firewall policy. Overrides default policy of any -> any | string |
"" |
no |
firewall_policy_tags | Additional tags for the firewall policy | map(string) |
{} |
no |
firewall_subnet_route_table_tags | Additional tags for the firewall subnet route tables | map(string) |
{} |
no |
firewall_subnet_suffix | Suffix to append to firewall subnet name | string |
"network-firewall" |
no |
firewall_subnet_tags | Additional tags for the firewall subnets | map(string) |
{} |
no |
firewall_subnets | A list of firewall subnet cidr blocks inside the VPC. Firewall subnets should not be larger than /28. Nothing else can live in these subnets. | list(string) |
[] |
no |
firewall_tags | Additional tags for the firewall | map(string) |
{} |
no |
flow_log_bucket_arn | Optional arn of s3 bucket destination for flow logs. If not specified, a bucket will be created. | string |
"" |
no |
home_net | summary cidr block for all resources behind this egress vpc | string |
"10.0.0.0/8" |
no |
http_ports | Destination ports for HTTP traffic | list(string) |
[ |
no |
igw_tags | Additional tags for the internet gateway | map(string) |
{} |
no |
kms_master_key_id | AWS KMS master key id used for bucket and log storage encryption. If empty, this module will use the default AWS-managed kms service key. | string |
"" |
no |
log_retention_days | Number of days to retain cloudwatch logs, default is 90 | number |
90 |
no |
name | Name to be used on all the resources as identifier | string |
"anwf" |
no |
nat_eip_tags | Additional tags for the NAT EIP | map(string) |
{} |
no |
nat_gateway_tags | Additional tags for the NAT gateways | map(string) |
{} |
no |
prefix | prefix prepended to certain resource names for organizational clarity | string |
"fncz" |
no |
private_route_table_routes | Configuration block of routes. See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_route_table#route | list(map(string)) |
[] |
no |
public_acl_tags | Additional tags for the public subnets network ACL | map(string) |
{} |
no |
public_subnet_route_table_tags | Additional tags for the public subnet route tables | map(string) |
{} |
no |
public_subnet_suffix | Suffix to append to public subnet name | string |
"public" |
no |
public_subnet_tags | Additional tags for the public subnets | map(string) |
{} |
no |
public_subnets | A list of public subnet cidr blocks inside the VPC | list(string) |
[] |
no |
s3_log_bucket | Name of s3 bucket for s3 bucket access logs | string |
"" |
no |
s3_logs | send firewall logs to s3 bucket. If unset, logs will not be sent. Allowed values are "ALERT" and "FLOW". | string |
"" |
no |
tags | A map of tags to add to all resources | map(string) |
{} |
no |
tls_ports | Destination ports for TLS traffic | list(string) |
[ |
no |
transit_gateway_acl_tags | Additional tags for the transit gateway subnets network ACL | map(string) |
{} |
no |
transit_gateway_id | id of the transit gateway for tgw subnet attachment | string |
n/a | yes |
transit_gateway_subnet_route_table_tags | Additional tags for the transit gateway subnet route tables | map(string) |
{} |
no |
transit_gateway_subnet_suffix | Suffix to append to transit gateway subnet name | string |
"transit-gateway" |
no |
transit_gateway_subnet_tags | Additional tags for the transit gateway subnets | map(string) |
{} |
no |
transit_gateway_subnets | A list of transit gateway attached subnet cidr blocks inside the VPC | list(string) |
[] |
no |
vpc_flow_logs | Log vpc traffic flows. If unset, vpc traffic flows will not be logged. If set, all vpc traffic will be logged. Allowed values are "CLOUDWATCH" and "S3". | string |
"" |
no |
vpc_tags | Additional tags for the VPC | map(string) |
{} |
no |
Name | Description |
---|---|
firewall_attachments | output used to inspect firewall endpoint ids for route creation |
network_firewall_endpoint_id | Created Network Firewall endpoint id |
transit_gateway_subnet_ids | transit gateway subnet ids |
vpce_lookup | Provided for troubleshooting so you can access per-az maps of anwf enis |