Deploy AWS Network Firewall on Multi-VPC environment with open source tools (P3)
Tags: devops, aws, terraform, github
Published: 2022-10-29 14:06:28

### Architecture

- Reference : https://aws.amazon.com/vi/blogs/networking-and-content-delivery/deployment-models-for-aws-network-firewall/

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jysow5vjhoz4c13bcxf0.png)

### Deploy Multi-VPC

- Reference : https://github.com/tnx-journey-to-cloud/terraform-aws-vpc

- We create a VPC module with Terraform to reuse and call it whenever we deploy a VPC in Multi-VPC environment such as Spoke, Ingress, Egress, ...

_main.tf_

```
resource "aws_vpc" "vpc" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_support   = var.enable_dns_support
  enable_dns_hostnames = var.enable_dns_hostnames
  instance_tenancy     = var.instance_tenancy

  tags = merge(
    var.tags,
    {
      "Name" : "${var.prefix}-vpc-${var.vpc_name}"
    }
  )
}

resource "aws_subnet" "this" {
  for_each = var.subnets

  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = each.value.cidr
  availability_zone       = each.value.az
  map_public_ip_on_launch = true

  tags = merge(
    {
      "Name" = "${var.prefix}-${each.key}-${each.value.az}"
    },
    var.tags,
    each.value.subnet_tags
  )
  lifecycle {
    create_before_destroy = true
  }
}


resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.${data.aws_region.current.name}.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [for k, v in aws_route_table.this : v.id]

  tags = merge(
    {
      "Name" = "${var.prefix}-s3-gwendpoint-${data.aws_region.current.name}"
    }
  )

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_vpc_endpoint" "dynamodb" {
  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.${data.aws_region.current.name}.dynamodb"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [for k, v in aws_route_table.this : v.id]

  tags = merge(
    {
      "Name" = "${var.prefix}-s3-gwendpoint-${data.aws_region.current.name}"
    }
  )
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group" "endpoint" {
  description = "Allow ECR inbound traffic"
  vpc_id      = aws_vpc.vpc.id
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.vpc.cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(
    {
      "Name" = "${var.prefix}-sg-vpce-${data.aws_region.current.name}"
    }
  )
}

resource "aws_vpc_endpoint" "endpoint" {
  depends_on = [
    aws_subnet.this
  ]

  for_each = var.endpoints

  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.${data.aws_region.current.name}.${each.key}"
  vpc_endpoint_type = "Interface"

  security_group_ids = [
    aws_security_group.endpoint[0].id,
  ]

  subnet_ids          = [for k, v in aws_subnet.this : v.id]
  private_dns_enabled = true

  tags = merge(
    {
      "Name" = "${var.prefix}-${replace(each.key, ".", "-")}-int-endpoint-${data.aws_region.current.name}"
    }
  )
}

```

_variables.tf_

```
variable "prefix" {
  description = "The description for each environment, ie: bin-dev"
  type        = string
}
variable "enable_dns_hostnames" {
  description = "(Optional) A boolean flag to enable/disable DNS support in the VPC. Defaults true."
  type        = bool
  default     = true
}

variable "enable_dns_support" {
  description = "(Optional) A boolean flag to enable/disable DNS hostnames in the VPC. Defaults false."
  type        = bool
  default     = true
}

variable "instance_tenancy" {
  description = "(Optional) A tenancy option for instances launched into the VPC. Default is default, which ensures that EC2 instances launched in this VPC use the EC2 instance tenancy attribute specified when the EC2 instance is launched. The only other option is dedicated, which ensures that EC2 instances launched in this VPC are run on dedicated tenancy instances regardless of the tenancy attribute specified at launch. This has a dedicated per region fee of $2 per hour, plus an hourly per instance usage fee."
  type        = string
  default     = "default"
}

variable "tags" {
  description = "The tags for the resources"
  type        = map(any)
  default     = {}
}

variable "vpc_name" {
  description = "The VPC name"
  type        = string
}


variable "vpc_cidr_block" {
  description = "The CIDR block for the VPC"
  type        = string
}

variable "subnets" {
  description = "Public subnet setting, cidr and availability zone (az) name are required"
  type = map(object({
    cidr             = string
    az               = string
    route_table_name = string
    subnet_tags      = optional(map(any))
  }))
variable "endpoints" {
  type        = set(string)
  description = "Set of  predefined Service names for creating VPC Interface Endpoints"
}
variable "igw_config" {
  description = "Optional, if set this vpc will create internet gw and route to rtb_name"
  type        = map(any)
  default = {
    igw_enable = false
    rtb_name   = ""
  }
}

variable "ngw_config" {
  description = "Optional, if set this vpc will create nat gw and route to rtb_name"
  type        = map(any)
  default = {
    ngw_enable  = false
    subnet_name = "" # subnetname that ngw will attach
    rtb_name    = "" # route table that ngw will attach routes to
  }

}
variable "route_table_setting" {
  type        = any
  description = "(required) route table infomation"
}

```

_outputs.tf_

```
output "vpc_id" {
  description = "The ID of the VPC"
  value       = try(aws_vpc.vpc.id, "")
}

output "vpc_arn" {
  description = "The ARN of the VPC"
  value       = try(aws_vpc.vpc.arn, "")
}

output "vpc_owner_id" {
  description = "The ID of the AWS account that owns the VPC"
  value       = try(aws_vpc.vpc.owner_id, "")
}

output "subnet_ids" {
  description = "List of IDs of subnets"
  value       = { for k, v in aws_subnet.this : k => v.id }
}

output "subnet_arns" {
  description = "List of ARNs of subnets"
  value       = { for k, v in aws_subnet.this : k => v.arn }
}

output "subnets_cidr_blocks" {
  description = "List of cidr_blocks of subnets"
  value       = { for k, v in aws_subnet.this : k => v.cidr_block }
}

output "route_table_ids" {
  description = "List of IDs of route tables"
  value       = { for k, v in aws_route_table.this : k => v.id }
}

output "route_table_association_ids" {
  description = "List of IDs of the route table association"
  value = {
    for k, v in aws_route_table_association.this : k => v.id
  }
}

```

_routetables.tf_

```
resource "aws_internet_gateway" "igw" {
  count = try(var.igw_config["igw_enable"], false) ? 1 : 0

  vpc_id = aws_vpc.vpc.id

  tags = merge(
    {
      "Name" : "${var.prefix}-igw-${var.vpc_name}"
    }
  )
}
resource "aws_route" "public_internet_gateway" {
  count                  = try(length(var.igw_config["rtb_name"]), 0) > 0 ? 1 : 0
  route_table_id         = aws_route_table.this[var.igw_config["rtb_name"]].id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.igw[0].id

  timeouts {
    create = "5m"
  }
  depends_on = [
    aws_internet_gateway.igw[0],
    aws_route_table.this
  ]
}
resource "aws_route_table" "this" {
  for_each = toset([for rtb in var.subnets : rtb["route_table_name"]])
  vpc_id   = aws_vpc.vpc.id

  dynamic "route" {
    for_each = try({ for k, v in var.route_table_setting[each.key] : k => v }, {})

    content {
      # One of the following destinations must be provided
      cidr_block      = route.value.cidr_block
      ipv6_cidr_block = lookup(route.value, "ipv6_cidr_block", null)

      # One of the following targets must be provided
      egress_only_gateway_id    = lookup(route.value, "egress_only_gateway_id", null)
      gateway_id                = lookup(route.value, "gateway_id", null)
      instance_id               = lookup(route.value, "instance_id", null)
      nat_gateway_id            = lookup(route.value, "nat_gateway_id", null)
      network_interface_id      = lookup(route.value, "network_interface_id", null)
      transit_gateway_id        = lookup(route.value, "transit_gateway_id", null)
      vpc_endpoint_id           = lookup(route.value, "vpc_endpoint_id", null)
      vpc_peering_connection_id = lookup(route.value, "vpc_peering_connection_id", null)
    }
  }

  timeouts {
    create = "5m"
    update = "5m"
  }
  tags = merge(
    {
      "Name" : replace("${var.prefix}-rtb-${var.vpc_name}-${each.value}", "_", "-")
    }
  )

}
resource "aws_route_table_association" "this" {
  for_each = var.subnets
  depends_on = [
    aws_route_table.this
  ]
  subnet_id      = aws_subnet.this[each.key].id
  route_table_id = aws_route_table.this[each.value.route_table_name].id
}

###############################################
##  NAT GW
###############################################
resource "aws_eip" "nat_gw_eip" {
  count = try(var.ngw_config["ngw_enable"], false) ? 1 : 0
  tags = merge(
    {
      "Name" : "${var.prefix}-igw-${var.vpc_name}"
    }
  )
}
resource "aws_nat_gateway" "ngw" {
  count         = try(var.ngw_config["ngw_enable"], false) ? 1 : 0
  allocation_id = aws_eip.nat_gw_eip[0].id
  subnet_id     = aws_subnet.this[var.ngw_config["subnet_name"]].id
  depends_on    = [aws_internet_gateway.igw[0]]
  tags = merge(
    {
      "Name" : "${var.prefix}-igw-${var.vpc_name}"
    }
  )
}
resource "aws_route" "nat_gateway" {
  count                  = try(length(var.ngw_config["rtb_name"]), 0) > 0 ? 1 : 0
  route_table_id         = aws_route_table.this[var.ngw_config["rtb_name"]].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.ngw[0].id

  timeouts {
    create = "5m"
  }
  depends_on = [
    aws_nat_gateway.ngw[0],
    aws_route_table.this
  ]
}

```

### Deploy Transit Gateway

- Reference : https://github.com/hungran/terraform-aws-tgw

_main.tf_

```
################################################################################
# Transit Gateway
################################################################################

resource "aws_ec2_transit_gateway" "tgw" {
  description                     = coalesce(var.description, var.tgw_name)
  amazon_side_asn                 = "64512"
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"
  auto_accept_shared_attachments  = "enable"
  multicast_support               = var.enable_mutlicast_support ? "enable" : "disable"
  vpn_ecmp_support                = var.enable_vpn_ecmp_support ? "enable" : "disable"
  dns_support                     = "enable"
  transit_gateway_cidr_blocks     = var.transit_gateway_cidr_blocks

  timeouts {
    create = try(var.timeouts.create, null)
    update = try(var.timeouts.update, null)
    delete = try(var.timeouts.delete, null)
  }

  tags = merge(
    var.tags,
    {
      "Name" : "${var.prefix}-tgw-${var.tgw_name}"
    }
  )
}

################################################################################
# Route Table / Routes
################################################################################

resource "aws_ec2_transit_gateway_route_table" "spoke" {
  transit_gateway_id = aws_ec2_transit_gateway.tgw.id

  tags = merge(
    var.tags,
    {
      "Name" : "${var.prefix}-tgw-${var.tgw_name}-rtb-spoke"
    }
  )
}

resource "aws_ec2_transit_gateway_route_table" "firewall" {
  transit_gateway_id = aws_ec2_transit_gateway.tgw.id

  tags = merge(
    var.tags,
    {
      "Name" : "${var.prefix}-tgw-${var.tgw_name}-rtb-firewall"
    }
  )
}

resource "aws_ec2_transit_gateway_vpc_attachment" "vpc_attachments" {
  for_each = var.vpc_attachments

  transit_gateway_id = aws_ec2_transit_gateway.tgw.id
  vpc_id             = each.value.vpc_id
  subnet_ids         = each.value.subnet_ids

  dns_support                                     = "enable"
  ipv6_support                                    = "disable"
  appliance_mode_support                          = "enable"
  transit_gateway_default_route_table_association = false
  transit_gateway_default_route_table_propagation = false
  tags = merge(
    var.tags,
    {
      "Name" : "${var.prefix}-tgw-${var.tgw_name}-vpc-attachment-${each.key}"
    }
  )
}

resource "aws_ec2_transit_gateway_route_table_association" "vpc_associate_spoke_rtb" {
  depends_on = [
    aws_ec2_transit_gateway_vpc_attachment.vpc_attachments
  ]
  for_each = aws_ec2_transit_gateway_vpc_attachment.vpc_attachments

  transit_gateway_attachment_id  = each.value.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spoke.id
}

resource "aws_ec2_transit_gateway_route_table_propagation" "vpc_propagate_firewall_rbt" {
  depends_on = [
    aws_ec2_transit_gateway_vpc_attachment.vpc_attachments
  ]
  for_each = aws_ec2_transit_gateway_vpc_attachment.vpc_attachments

  transit_gateway_attachment_id  = each.value.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.firewall.id
}

# resource "aws_ec2_transit_gateway_route" "vpn_routes" {
#   depends_on = [
#     aws_ec2_transit_gateway_route_table.vpn
#   ]
#   for_each = var.vpn_routes

#   transit_gateway_attachment_id  = var.transit_gateway_attachment_vpn_id
#   transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.vpn.id
#   destination_cidr_block         = each.value.vpn_routes
# }

resource "aws_ec2_transit_gateway_vpc_attachment" "inspection" {
  for_each = var.vpc_inspection_attachments

  transit_gateway_id = aws_ec2_transit_gateway.tgw.id
  vpc_id             = each.value.vpc_id
  subnet_ids         = each.value.subnet_ids

  dns_support                                     = "enable"
  ipv6_support                                    = "disable"
  appliance_mode_support                          = "enable"
  transit_gateway_default_route_table_association = false
  transit_gateway_default_route_table_propagation = false
  tags = merge(
    var.tags,
    {
      "Name" : "${var.prefix}-tgw-${var.tgw_name}-inspection-vpc-attachment-${each.key}"
    }
  )
}

resource "aws_ec2_transit_gateway_route_table_association" "vpc_inspection_associate_rtb" {
  depends_on = [
    aws_ec2_transit_gateway_vpc_attachment.inspection
  ]
  for_each = aws_ec2_transit_gateway_vpc_attachment.inspection

  transit_gateway_attachment_id  = each.value.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.firewall.id
}

resource "aws_ec2_transit_gateway_route" "inspection" {
  depends_on = [
    aws_ec2_transit_gateway_vpc_attachment.inspection
  ]
  for_each = aws_ec2_transit_gateway_vpc_attachment.inspection

  destination_cidr_block         = "0.0.0.0/0"
  transit_gateway_attachment_id  = each.value.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spoke.id
}

```

_variables.tf_

```
variable "amazon_side_asn" {
  description = "The Autonomous System Number (ASN) for the AWS side of a Border Gateway Protocol (BGP) session. You can use the default ASN, or you can specify a private ASN in the 64512-65534 or 4200000000-4294967294 ranges."
  type        = number
  default     = 64512
}

variable "description" {
  description = "Set the description of your transit gateway to help you identify it in the future."
  type        = string
  default     = ""
}

variable "enable_mutlicast_support" {
  description = "Enables the ability to create multicast domains in this transit gateway."
  type        = bool
  default     = false
}

variable "enable_vpn_ecmp_support" {
  description = "Equal cost multipath (ECMP) routing for VPN Connections that are attached to this transit gateway."
  type        = bool
  default     = true
}

variable "prefix" {
  description = "The descriptio for each environment, ie: bin-dev"
  type        = string
}

variable "tags" {
  description = "The tags for the resources"
  type        = map(any)
  default     = {}
}

variable "tgw_name" {
  description = "TGW Name"
  type        = string
}

variable "transit_gateway_cidr_blocks" {
  description = "One or more IPv4 or IPv6 CIDR blocks for the transit gateway. Must be a size /24 CIDR block or larger for IPv4, or a size /64 CIDR block or larger for IPv6"
  type        = list(string)
  default     = []
}

variable "timeouts" {
  description = "Create, update, and delete timeout configurations for the transit gateway"
  type        = map(string)
  default     = {}
}

variable "vpc_attachments" {
  description = "vpc and subnet ids for attachment transit gateway"
  type = map(object({
    vpc_id     = string
    subnet_ids = list(string)
  }))
}

variable "vpc_inspection_attachments" {
  description = "vpc and subnet ids for attachment transit gateway"
  type = map(object({
    vpc_id     = string
    subnet_ids = list(string)
  }))
}

```

_outputs.tf_

```
output "tgw_spoke_route_table_ids" {
  value = aws_ec2_transit_gateway_route_table.spoke.id
}

output "tgw_firewall_route_table_ids" {
  value = aws_ec2_transit_gateway_route_table.firewall.id
}

output "tgw_id" {
  value = aws_ec2_transit_gateway.tgw.id
}

output "tgw_vpc_attachment_ids" {
  value = { for k, v in aws_ec2_transit_gateway_vpc_attachment.vpc_attachments : k => v.id }
}

output "tgw_vpc_inspection_attachment_ids" {
  value = { for k, v in aws_ec2_transit_gateway_vpc_attachment.inspection : k => v.id }
}

```


### Deploy AWS Network Firewall 

- Reference :  https://github.com/tnx-journey-to-cloud/terraform-aws-networkfirewall

_main.tf_

```
resource "aws_networkfirewall_firewall" "this" {
  name                = "${var.prefix}-nfw-${var.nfw_name}"
  description         = coalesce(var.description, var.nfw_name)
  firewall_policy_arn = aws_networkfirewall_firewall_policy.this.arn
  vpc_id              = var.vpc_id

  firewall_policy_change_protection = var.firewall_policy_change_protection
  subnet_change_protection          = var.subnet_change_protection

  dynamic "subnet_mapping" {
    for_each = toset(var.subnet_mapping)

    content {
      subnet_id = subnet_mapping.value
    }
  }

  tags = var.tags
}

################# new suricata rule group #################
resource "aws_networkfirewall_rule_group" "suricata_stateful_group" {
  count = length(var.suricata_stateful_rule_group) > 0 ? length(var.suricata_stateful_rule_group) : 0
  type  = "STATEFUL"

  name        = var.suricata_stateful_rule_group[count.index]["name"]
  description = var.suricata_stateful_rule_group[count.index]["description"]
  capacity    = var.suricata_stateful_rule_group[count.index]["capacity"]
  rule_group {
    rules_source {
      rules_string = try(file(var.suricata_stateful_rule_group[count.index]["rules_file"]), "")
    }
    dynamic "rule_variables" {
      for_each  = lookup(var.suricata_stateful_rule_group[count.index],"rule_variables",{})
      content {
        dynamic "ip_sets" {
          for_each = rule_variables.key == "ip_sets" ? rule_variables.value : []
          content {
            key = ip_sets.value["key"]
            ip_set {
              definition = ip_sets.value["ip_set"]
            }
          }
        }

        dynamic "port_sets" {
          for_each = rule_variables.key == "port_sets" ? rule_variables.value : []
          content {
            key = port_sets.value["key"]
            port_set {
              definition = port_sets.value["port_sets"]
            }
          }
        }
      }
    }
  }

  tags = merge(var.tags)
}
#####################################################################################

resource "aws_networkfirewall_rule_group" "domain_stateful_group" {
  count = length(var.domain_stateful_rule_group) > 0 ? length(var.domain_stateful_rule_group) : 0
  type  = "STATEFUL"

  name        = var.domain_stateful_rule_group[count.index]["name"]
  description = var.domain_stateful_rule_group[count.index]["description"]
  capacity    = var.domain_stateful_rule_group[count.index]["capacity"]

  rule_group {
    dynamic "rule_variables" {
      for_each  = lookup(var.domain_stateful_rule_group[count.index],"rule_variables",[])
      content {
        ip_sets {
          key = rule_variables.value["key"]
          ip_set {
            definition = rule_variables.value["ip_set"]
          }
        }
      }
    }

    rules_source {
      rules_source_list {
        generated_rules_type = var.domain_stateful_rule_group[count.index]["actions"]
        target_types         = var.domain_stateful_rule_group[count.index]["protocols"]
        targets              = var.domain_stateful_rule_group[count.index]["domain_list"]
      }
    }
  }

  tags = merge(var.tags)
}


resource "aws_networkfirewall_rule_group" "fivetuple_stateful_group" {
  count = length(var.fivetuple_stateful_rule_group) > 0 ? length(var.fivetuple_stateful_rule_group) : 0
  type  = "STATEFUL"

  name        = var.fivetuple_stateful_rule_group[count.index]["name"]
  description = var.fivetuple_stateful_rule_group[count.index]["description"]
  capacity    = var.fivetuple_stateful_rule_group[count.index]["capacity"]

  rule_group {
    rules_source {
      dynamic "stateful_rule" {
        for_each = var.fivetuple_stateful_rule_group[count.index].rule_config
        content {
          action = upper(stateful_rule.value.actions["type"])
          header {
            destination      = stateful_rule.value.destination_ipaddress
            destination_port = stateful_rule.value.destination_port
            direction        = upper(stateful_rule.value.direction)
            protocol         = upper(stateful_rule.value.protocol)
            source           = stateful_rule.value.source_ipaddress
            source_port      = stateful_rule.value.source_port
          }
          rule_option {
            keyword = "sid"
            settings = ["${stateful_rule.value.sid}; msg:\"${try(stateful_rule.value.description,"")}\""]
          }
        }
      }
    }
  }

  tags = merge(var.tags)
}



resource "aws_networkfirewall_rule_group" "stateless_group" {
  count = length(var.stateless_rule_group) > 0 ? length(var.stateless_rule_group) : 0
  type  = "STATELESS"

  name        = var.stateless_rule_group[count.index]["name"]
  description = var.stateless_rule_group[count.index]["description"]
  capacity    = var.stateless_rule_group[count.index]["capacity"]

  rule_group {
    rules_source {
      stateless_rules_and_custom_actions {
        dynamic "stateless_rule" {
          for_each = var.stateless_rule_group[count.index].rule_config
          content {
            priority = stateless_rule.value.priority
            rule_definition {
              actions = ["aws:${stateless_rule.value.actions["type"]}"]
              match_attributes {
                source {
                  address_definition = stateless_rule.value.source_ipaddress
                }
                # If protocol is TCP : 6 or UDP :17 get source ports from variables and set in source_port block
                dynamic "source_port" {
                  for_each = contains(stateless_rule.value.protocols_number, 6) || contains(stateless_rule.value.protocols_number, 17) ? try(toset([{
                       from = stateless_rule.value.source_from_port, 
                       to   = stateless_rule.value.source_to_port
                  }]), [] ) : []
                  content {
                    from_port = source_port.value.from
                    to_port = source_port.value.to
                  }
                }
                destination {
                  address_definition = stateless_rule.value.destination_ipaddress
                }
                # If protocol is TCP : 6 or UDP :17 get destination ports from variables and set in destination_port block
                dynamic "destination_port" {
                  for_each = contains(stateless_rule.value.protocols_number, 6) || contains(stateless_rule.value.protocols_number, 17) ? try(toset([{
                       from = stateless_rule.value.destination_from_port,
                       to   = stateless_rule.value.destination_to_port
                  }]), [] ) : []
                  content {
                    from_port = destination_port.value.from
                    to_port = destination_port.value.to
                  }
                }
                protocols = stateless_rule.value.protocols_number
                tcp_flag {
                  flags = stateless_rule.value.tcp_flag["flags"]
                  masks = stateless_rule.value.tcp_flag["masks"]
                }
              }
            }
          }
        }
      }
    }
  }


  tags = merge(var.tags)
}
resource "aws_networkfirewall_firewall_policy" "this" {
  name = "${var.prefix}-nfw-policy-${var.nfw_name}"

  firewall_policy {
    stateless_default_actions          = ["aws:${var.stateless_default_actions}"]
    stateless_fragment_default_actions = ["aws:${var.stateless_fragment_default_actions}"]

    #Stateless Rule Group Reference
    dynamic "stateless_rule_group_reference" {
      for_each = local.this_stateless_group_arn
      content {
        # Priority is sequentially as per index in stateless_rule_group list
        priority     = index(local.this_stateless_group_arn, stateless_rule_group_reference.value) + 1
        resource_arn = stateless_rule_group_reference.value
      }
    }

    #StateFul Rule Group Reference
    dynamic "stateful_rule_group_reference" {
      for_each = local.this_stateful_group_arn
      content {
        resource_arn = stateful_rule_group_reference.value
      }
    }
  }
  tags = merge(var.tags)
}

###################### Logging Config ######################

resource "aws_cloudwatch_log_group" "nfw" {
  for_each = try(var.logging_config, {})
  name = "/aws/network-firewall/${each.key}"

  tags = merge(var.tags)

  retention_in_days = each.value.retention_in_days
}

resource "aws_networkfirewall_logging_configuration" "this" {
  count = try(length(var.logging_config),0) > 0 ? 1 : 0
  firewall_arn = aws_networkfirewall_firewall.this.arn
  logging_configuration {
    dynamic "log_destination_config" {
      for_each = var.logging_config
      content {
        log_destination = {
          logGroup = aws_cloudwatch_log_group.nfw[log_destination_config.key].name
        }
        log_destination_type = "CloudWatchLogs"
        log_type             = upper(log_destination_config.key)
      }

    }
  }
}

```

_variables.tf_

```
variable "prefix" {
  description = "The descriptio for each environment, ie: bin-dev"
  type        = string
}

variable "tags" {
  description = "The tags for the resources"
  type        = map(any)
  default     = {}
}

variable "description" {
  default = ""
}

variable "fivetuple_stateful_rule_group" {
  description = "Config for 5-tuple type stateful rule group"
  type        = list(any)
  default     = []
}

variable "domain_stateful_rule_group" {
  description = "Config for domain type stateful rule group"
  type        = list(any)
  default     = []
}

variable "suricata_stateful_rule_group" {
  description = "Config for Suricata type stateful rule group"
  type        = list(any)
  default     = []
}

variable "stateless_rule_group" {
  description = "Config for stateless rule group"
  type = list(any)
}

variable "nfw_name" {
  description = "firewall name"
  type        = string
  default     = "example"
}

variable "subnet_mapping" {
  description = "Subnet ids mapping to have individual firewall endpoint"
}

variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "stateless_default_actions" {
  description = "Default stateless Action"
  type = string
  default = "forward_to_sfe"
}

variable "stateless_fragment_default_actions" {
  description = "Default Stateless action for fragmented packets"
  type = string
  default = "forward_to_sfe"
}

variable "firewall_policy_change_protection" {
  type = string
  description = "(optional) we set false because we apply gitops for this"
  default = false
}

variable "subnet_change_protection" {
  type = string
  description = "(optional) we set false because we apply gitops for this"
  default = false
}

variable "logging_config" {
  type = map(any)
  description = "(optional) Logging config for network firewall"
  default = {}
}

```

_outputs.tf_

```
output "id" {
  description = "Created Network Firewall ID from network_firewall module"
  value       = aws_networkfirewall_firewall.this.id
}

output "arn" {
  description = "Created Network Firewall ARN from network_firewall module"
  value       = aws_networkfirewall_firewall.this.arn
}

output "endpoint_id" {
  description = "Created Network Firewall endpoint id"
  value       = flatten(aws_networkfirewall_firewall.this.firewall_status[*].sync_states[*].*.attachment[*])[*].endpoint_id
}


```

**Our Cloud Journey's members:**
- https://github.com/hungran
- https://github.com/hiimtung
- https://github.com/kerashanog
- https://github.com/haicasgox
- https://github.com/lacoski
- https://github.com/HieuChayA4