Skip to content

Instantly share code, notes, and snippets.

@huynhbaoan
Created December 30, 2024 07:43
Show Gist options
  • Select an option

  • Save huynhbaoan/dfd8b617e47edec0b5baf098d3bb5193 to your computer and use it in GitHub Desktop.

Select an option

Save huynhbaoan/dfd8b617e47edec0b5baf098d3bb5193 to your computer and use it in GitHub Desktop.
Below is a complete example of how to implement AWS Network Firewall with separate rule group resources, a single firewall policy, and a lightweight firewall module—all while enforcing CloudWAN service insertion policies. We’ll illustrate:
1. A firewall module that only creates the firewall itself.
2. NP MEL prot firewall example:
• Multiple rule groups with different Suricata rules.
• A single firewall policy referencing those rule groups.
• A firewall resource (instantiated via the module).
3. A CloudWAN routing policy JSON showing how traffic is forced (via “STRICT” mode) to the NP MEL prot firewall segment.
Note: Adjust resource names and IDs to match your actual environment.
We focus on the NP MEL protected firewall scenario, but you can replicate the pattern for ext, int, or other environment/region combos.
1. Firewall Module
A lightweight module that only creates the aws_networkfirewall_firewall resource, referencing an existing firewall policy. It expects the policy ARN, the VPC ID, and the subnets for the firewall endpoints.
<details>
<summary><code>modules/firewall/variables.tf</code></summary>
variable "name" {
type = string
description = "Name prefix or identifier for this firewall"
}
variable "vpc_id" {
type = string
description = "VPC ID where the firewall is deployed"
}
variable "subnet_ids" {
type = list(string)
description = "Subnet IDs for firewall endpoints in each AZ"
}
variable "firewall_policy_arn" {
type = string
description = "ARN of the firewall policy to associate"
}
variable "tags" {
type = map(string)
default = {}
description = "Tags for the firewall"
}
</details>
<details>
<summary><code>modules/firewall/main.tf</code></summary>
resource "aws_networkfirewall_firewall" "this" {
name = "${var.name}-firewall"
firewall_policy_arn = var.firewall_policy_arn
vpc_id = var.vpc_id
dynamic "subnet_mapping" {
for_each = var.subnet_ids
content {
subnet_id = subnet_mapping.value
}
}
# For demonstration, we turn off the various protections
delete_protection = false
firewall_policy_change_protection = false
subnet_change_protection = false
tags = var.tags
}
</details>
<details>
<summary><code>modules/firewall/outputs.tf</code></summary>
output "firewall_name" {
value = aws_networkfirewall_firewall.this.name
description = "Name of the Network Firewall"
}
output "firewall_arn" {
value = aws_networkfirewall_firewall.this.arn
description = "ARN of the Network Firewall"
}
</details>
That’s it for the module—just the firewall resource.
2. NP MEL Prot Firewall Example
We’ll create:
1. Multiple rule groups (stateful) with Suricata rules.
2. A single firewall policy referencing those rule groups.
3. Instantiate the firewall module to tie it all together.
We’ll do this in, say, three files inside your np/ folder:
• np_mel_prot_rule_groups.tf
• np_mel_prot_firewall_policy.tf
• np_mel_prot_firewall.tf
2.1. Rule Groups
Suppose you want:
• Rule group 1: Block UDP, Allow HTTP
• Rule group 2: Allow ICMP from anywhere
<details>
<summary><code>np_mel_prot_rule_groups.tf</code></summary>
##############################
# 1) BLOCK UDP, ALLOW HTTP
##############################
resource "aws_networkfirewall_rule_group" "np_mel_prot_block_udp_allow_http" {
name = "np-mel-prot-block-udp-allow-http"
capacity = 100
type = "STATEFUL"
rule_group {
rules_source {
rules_string = <<EOF
# Block all UDP
drop udp any any -> any any (msg:"Block all UDP traffic"; sid:10001;)
# Allow HTTP (port 80)
alert tcp any any -> any 80 (msg:"Allow HTTP on port 80"; sid:10002;)
EOF
}
}
}
##############################
# 2) ALLOW ICMP
##############################
resource "aws_networkfirewall_rule_group" "np_mel_prot_allow_icmp" {
name = "np-mel-prot-allow-icmp"
capacity = 100
type = "STATEFUL"
rule_group {
rules_source {
rules_string = <<EOF
# Allow ICMP (ping)
alert icmp any any -> any any (msg:"Allow ICMP traffic"; sid:20001;)
EOF
}
}
}
</details>
Terraform Validate: If the Suricata block is syntactically invalid (e.g., missing parentheses), Terraform or the AWS API will throw an error when you run terraform plan/apply.
2.2. Firewall Policy
We reference both rule groups in a single policy. The priority determines the order of evaluation.
<details>
<summary><code>np_mel_prot_firewall_policy.tf</code></summary>
resource "aws_networkfirewall_firewall_policy" "np_mel_prot_policy" {
name = "np-mel-prot-firewall-policy"
firewall_policy {
stateless_default_actions = ["aws:forward_to_sfe"]
stateless_fragment_default_actions = ["aws:forward_to_sfe"]
stateful_engine_options {
rule_order = "STRICT_ORDER"
}
stateful_rule_group_reference {
resource_arn = aws_networkfirewall_rule_group.np_mel_prot_block_udp_allow_http.arn
priority = 1
}
stateful_rule_group_reference {
resource_arn = aws_networkfirewall_rule_group.np_mel_prot_allow_icmp.arn
priority = 2
}
}
}
</details>
Add more rule groups as needed. For example, a third group to block SSH or allow only DNS, etc.
2.3. Instantiate the Firewall
Finally, we use our firewall module. The firewall_policy_arn points to the policy we just created.
<details>
<summary><code>np_mel_prot_firewall.tf</code></summary>
module "np_mel_prot_fw" {
source = "../modules/firewall"
name = "np-mel-prot"
vpc_id = var.np_mel_prot_fw_vpc_id
subnet_ids = var.np_mel_prot_fw_subnet_ids
firewall_policy_arn = aws_networkfirewall_firewall_policy.np_mel_prot_policy.arn
tags = {
Environment = "nonprod"
Region = "mel"
Firewall = "protected"
}
}
</details>
Replace var.np_mel_prot_fw_vpc_id and var.np_mel_prot_fw_subnet_ids with your real VPC/subnet references.
At this point, you have a running firewall in your NP MEL Protected VPC, with a firewall policy that includes two rule groups.
3. CloudWAN Routing Policy (Service Insertion)
Now we define the CloudWAN policy so that traffic from or to the “NP MEL prot segment” must pass through the “NP MEL prot fw segment.” Below is a partial JSON example. Adapt it to your full environment with all segments (int, ext, segr, etc.).
<details>
<summary><code>np_cloudwan_policy.json</code></summary>
{
"segments": [
{ "name": "np-mel-int-seg" },
{ "name": "np-mel-ext-seg" },
{ "name": "np-mel-prot-seg" },
{ "name": "np-mel-prot-fw-seg" }
// Add more segments for NP SYD, etc.
],
"coreNetworkConfiguration": {
"edgeLocations": ["ALL_REGIONS"],
"asnRanges": ["64512-64516"]
},
"segmentActions": [
// 1) NP MEL int -> NP MEL prot
{
"segmentName": "np-mel-int-seg",
"destinations": [
{
"segmentName": "np-mel-prot-seg",
"mode": "STRICT",
"attachmentName": "np-mel-prot-fw-seg"
}
]
},
// Symmetric return
{
"segmentName": "np-mel-prot-seg",
"destinations": [
{
"segmentName": "np-mel-int-seg",
"mode": "STRICT",
"attachmentName": "np-mel-prot-fw-seg"
}
]
},
// 2) NP MEL ext -> NP MEL prot
{
"segmentName": "np-mel-ext-seg",
"destinations": [
{
"segmentName": "np-mel-prot-seg",
"mode": "STRICT",
"attachmentName": "np-mel-prot-fw-seg"
}
]
},
// Symmetric return
{
"segmentName": "np-mel-prot-seg",
"destinations": [
{
"segmentName": "np-mel-ext-seg",
"mode": "STRICT",
"attachmentName": "np-mel-prot-fw-seg"
}
]
}
// ... add cross-region flows, etc.
]
}
</details>
The main logic:
• mode = "STRICT" ensures traffic must pass through the firewall segment.
• If you want to chain multiple firewalls (e.g., NP SYD prot fw as well), you can add more steps.
Finally, in Terraform:
resource "aws_networkmanager_core_network_policy" "np_policy" {
core_network_id = aws_networkmanager_core_network.nonprod_core.id
policy_document = file("${path.module}/np_cloudwan_policy.json")
description = "CloudWAN policy for NP nonprod environment"
}
When the policy is active and each VPC is attached to the correct segment, traffic from np-mel-int-seg to np-mel-prot-seg is routed via np-mel-prot-fw-seg. The firewall then applies your Suricata rules (e.g., block UDP, allow HTTP, etc.).
Putting It All Together
1. Rule Groups are defined outside the firewall module. Each group has its own Suricata block.
2. Firewall Policy references each rule group, applying them in a priority order.
3. Firewall Module is a simple construct that binds the policy to the VPC subnets.
4. CloudWAN: Use a JSON-based core network policy to enforce service insertion (firewall).
Benefits
• Terraform Validation: If your rule group Suricata syntax or JSON is malformed, Terraform or AWS will fail at plan/apply time.
• Ease of Modification: Adding, removing, or updating a rule group is as simple as editing a single resource.
• Clear Separation: The firewall module doesn’t handle complex logic for rule groups; it just references the final policy ARN.
Final Notes
• You can repeat this pattern for NP SYD or PROD firewalls (int, ext, prot, segr, etc.) with their own sets of rule group resources and a single policy.
• If you want logging, add an aws_networkfirewall_logging_configuration resource referencing the firewall ARN.
• If you need stateless rule groups or advanced Suricata rules (like domain filtering), you can define them similarly—just change the type = "STATELESS" or more advanced rule syntax.
This design remains straightforward for engineers, with clear places to modify rules and a robust pipeline for Terraform to validate before changes go live..
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment