Skip to content

Commit d2adece

Browse files
Create a basic setup for a WAF
- DDoS provided OOTB - Add basic protection rules - Block known bad IPs - Add a rate limiter based on IP
1 parent 3aa6867 commit d2adece

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

terraform/app/variables.tf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,37 @@ variable "valkey_log_retention_days" {
462462
}
463463
}
464464

465+
variable "waf_rule_actions" {
466+
type = map(string)
467+
default = {
468+
core_rule_set = "COUNT"
469+
known_bad_inputs = "COUNT"
470+
ip_reputation_list = "COUNT"
471+
rate_limiting = "COUNT"
472+
}
473+
description = "Map of WAF rule actions (COUNT/BLOCK)"
474+
validation {
475+
condition = alltrue([
476+
for action in values(var.waf_rule_actions) : contains(["COUNT", "BLOCK"], action)
477+
])
478+
error_message = "Valid values: COUNT or BLOCK"
479+
}
480+
}
481+
482+
variable "waf_logging_enabled" {
483+
type = bool
484+
default = true
485+
description = "Enable WAF request logging"
486+
nullable = false
487+
}
488+
489+
variable "waf_rate_limit_threshold" {
490+
type = number
491+
default = 100
492+
description = "Rate limit threshold for WAF in requests per 5 minutes"
493+
nullable = false
494+
}
495+
465496

466497
variable "active_target_group" {
467498
default = "blue"

terraform/app/waf.tf

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
resource "aws_wafv2_web_acl" "mavis_waf" {
2+
name = "mavis-waf-${var.environment}"
3+
description = "WAF ACL for Mavis application"
4+
scope = "REGIONAL"
5+
6+
default_action {
7+
allow {}
8+
}
9+
10+
visibility_config {
11+
cloudwatch_metrics_enabled = var.waf_logging_enabled
12+
metric_name = "mavis-waf-${var.environment}"
13+
sampled_requests_enabled = var.waf_logging_enabled
14+
}
15+
16+
lifecycle {
17+
ignore_changes = [rule]
18+
}
19+
}
20+
21+
resource "aws_wafv2_web_acl_association" "alb_association" {
22+
resource_arn = aws_lb.app_lb.arn
23+
web_acl_arn = aws_wafv2_web_acl.mavis_waf.arn
24+
25+
depends_on = [aws_wafv2_web_acl_rule_group_association.rate_limit]
26+
}
27+
28+
resource "aws_cloudwatch_log_group" "waf_logs" {
29+
count = var.waf_logging_enabled ? 1 : 0
30+
name = "aws-waf-logs-mavis-${var.environment}"
31+
retention_in_days = 30
32+
}
33+
34+
resource "aws_wafv2_web_acl_logging_configuration" "mavis_waf_logging" {
35+
count = var.waf_logging_enabled ? 1 : 0
36+
log_destination_configs = [aws_cloudwatch_log_group.waf_logs[0].arn]
37+
resource_arn = aws_wafv2_web_acl.mavis_waf.arn
38+
39+
depends_on = [aws_cloudwatch_log_group.waf_logs]
40+
}
41+
42+
######### WAF RULE GROUP ASSOCIATIONS #########
43+
44+
resource "aws_wafv2_web_acl_rule_group_association" "ip_reputation_list" {
45+
rule_name = "AWSManagedRulesAmazonIpReputationList"
46+
priority = 10
47+
web_acl_arn = aws_wafv2_web_acl.mavis_waf.arn
48+
49+
managed_rule_group {
50+
name = "AWSManagedRulesAmazonIpReputationList"
51+
vendor_name = "AWS"
52+
}
53+
54+
override_action = var.waf_rule_actions["ip_reputation_list"] == "COUNT" ? "count" : "none"
55+
56+
depends_on = [aws_wafv2_web_acl.mavis_waf]
57+
}
58+
59+
resource "aws_wafv2_web_acl_rule_group_association" "common_rule_set" {
60+
rule_name = "AWSManagedRulesCommonRuleSet"
61+
priority = 20
62+
web_acl_arn = aws_wafv2_web_acl.mavis_waf.arn
63+
64+
managed_rule_group {
65+
name = "AWSManagedRulesCommonRuleSet"
66+
vendor_name = "AWS"
67+
}
68+
69+
override_action = var.waf_rule_actions["core_rule_set"] == "COUNT" ? "count" : "none"
70+
71+
depends_on = [aws_wafv2_web_acl_rule_group_association.ip_reputation_list]
72+
}
73+
74+
resource "aws_wafv2_web_acl_rule_group_association" "known_bad_inputs" {
75+
rule_name = "AWSManagedRulesKnownBadInputsRuleSet"
76+
priority = 30
77+
web_acl_arn = aws_wafv2_web_acl.mavis_waf.arn
78+
79+
managed_rule_group {
80+
name = "AWSManagedRulesKnownBadInputsRuleSet"
81+
vendor_name = "AWS"
82+
}
83+
84+
override_action = var.waf_rule_actions["known_bad_inputs"] == "COUNT" ? "count" : "none"
85+
86+
depends_on = [aws_wafv2_web_acl_rule_group_association.common_rule_set]
87+
}
88+
89+
resource "aws_wafv2_web_acl_rule_group_association" "rate_limit" {
90+
rule_name = "RateLimitRule"
91+
priority = 40
92+
web_acl_arn = aws_wafv2_web_acl.mavis_waf.arn
93+
94+
rule_group_reference {
95+
arn = aws_wafv2_rule_group.rate_limit_group.arn
96+
}
97+
depends_on = [aws_wafv2_web_acl_rule_group_association.known_bad_inputs]
98+
}
99+
100+
101+
######### CUSTOM RULE GROUPS #########
102+
103+
resource "aws_wafv2_rule_group" "rate_limit_group" {
104+
name = "mavis-rate-limit-${var.environment}"
105+
scope = "REGIONAL"
106+
capacity = 2
107+
108+
rule {
109+
name = "RateLimitRule"
110+
priority = 0
111+
112+
action {
113+
dynamic "count" {
114+
for_each = var.waf_rule_actions["rate_limiting"] == "COUNT" ? [1] : []
115+
content {}
116+
}
117+
dynamic "block" {
118+
for_each = var.waf_rule_actions["rate_limiting"] == "BLOCK" ? [1] : []
119+
content {}
120+
}
121+
}
122+
123+
statement {
124+
rate_based_statement {
125+
limit = var.waf_rate_limit_threshold
126+
aggregate_key_type = "IP"
127+
evaluation_window_sec = 60
128+
}
129+
}
130+
131+
visibility_config {
132+
cloudwatch_metrics_enabled = true
133+
metric_name = "RateLimitRule"
134+
sampled_requests_enabled = true
135+
}
136+
}
137+
138+
visibility_config {
139+
cloudwatch_metrics_enabled = true
140+
metric_name = "mavis-rate-limit-${var.environment}"
141+
sampled_requests_enabled = true
142+
}
143+
}

0 commit comments

Comments
 (0)