Skip to content

Commit f65a3de

Browse files
OlamideOl1claude
andcommitted
Add host_ip_restriction_rules to WAF module
Blocks requests to a specific Host unless the source IP is in a per-host allowed set (globally allowed IPs still pass). Additive and defaults to {}, so existing consumers are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2bf17d6 commit f65a3de

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

aws/waf/main.tf

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,65 @@ resource "aws_wafv2_web_acl" "main" {
234234
}
235235
}
236236

237+
dynamic "rule" {
238+
for_each = var.host_ip_restriction_rules
239+
content {
240+
name = "${rule.value["name"]}-host-ip-restriction"
241+
priority = rule.value["priority"]
242+
243+
dynamic "action" {
244+
for_each = rule.value["count_override"] == true ? [1] : []
245+
content {
246+
count {}
247+
}
248+
}
249+
dynamic "action" {
250+
for_each = rule.value["count_override"] == false ? [1] : []
251+
content {
252+
block {}
253+
}
254+
}
255+
256+
statement {
257+
and_statement {
258+
statement {
259+
byte_match_statement {
260+
field_to_match {
261+
single_header {
262+
name = "host"
263+
}
264+
}
265+
266+
positional_constraint = "EXACTLY"
267+
268+
search_string = lower(rule.value["host"])
269+
270+
text_transformation {
271+
priority = 0
272+
type = "LOWERCASE"
273+
}
274+
}
275+
}
276+
statement {
277+
not_statement {
278+
statement {
279+
ip_set_reference_statement {
280+
arn = aws_wafv2_ip_set.host_restriction[rule.key].arn
281+
}
282+
}
283+
}
284+
}
285+
}
286+
}
287+
288+
visibility_config {
289+
cloudwatch_metrics_enabled = true
290+
sampled_requests_enabled = true
291+
metric_name = "${rule.value["name"]}-host-ip-restriction"
292+
}
293+
}
294+
}
295+
237296
dynamic "rule" {
238297
for_each = var.aws_managed_rule_groups
239298
content {
@@ -330,6 +389,15 @@ resource "aws_wafv2_ip_set" "block_ip_list" {
330389
addresses = var.block_ip_list
331390
}
332391

392+
resource "aws_wafv2_ip_set" "host_restriction" {
393+
for_each = var.host_ip_restriction_rules
394+
395+
name = "${var.name}-${each.key}-allowed-ip-set"
396+
scope = var.waf_scope
397+
ip_address_version = "IPV4"
398+
addresses = each.value.allowed_ip_list
399+
}
400+
333401
module "cloudwatch_log_extract" {
334402
source = "../cloudwatch-log-extract"
335403

aws/waf/variables.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ variable "block_ip_list" {
7070
type = list(string)
7171
default = []
7272
}
73+
74+
variable "host_ip_restriction_rules" {
75+
description = "Restrict specific Hosts to an allowed IP set. For each entry, blocks requests where the Host header matches exactly AND the source IP is not in that entry's allowed set. Globally allowed IPs (allowed_ip_list) still pass via the priority-0 allow rule."
76+
type = map(object({
77+
name = string # Friendly name, used in the rule name and CloudWatch metric.
78+
priority = number # Unique WAF rule priority within the ACL. All rules are processed from lowest to highest priority.
79+
host = string # Exact Host header to restrict, e.g. "core-data.albaikcloud.com".
80+
allowed_ip_list = list(string) # IPv4 CIDRs permitted to reach the host. All other (non globally-allowed) IPs are blocked for this host.
81+
count_override = optional(bool, false) # If true, override the action to `count` (dry run). If false (default), the action is `block`.
82+
}))
83+
default = {}
84+
}

0 commit comments

Comments
 (0)