Created
December 30, 2024 07:43
-
-
Save huynhbaoan/dfd8b617e47edec0b5baf098d3bb5193 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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