Skip to content

Commit fd7e0fd

Browse files
committed
feat(infra): Add WAF implementation
1 parent 91c4d43 commit fd7e0fd

File tree

5 files changed

+299
-0
lines changed

5 files changed

+299
-0
lines changed

deployment/terraform/modules/aws/onyx/main.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,17 @@ module "eks" {
7575
private_cluster_enabled = var.private_cluster_enabled
7676
cluster_endpoint_public_access_cidrs = var.cluster_endpoint_public_access_cidrs
7777
}
78+
79+
module "waf" {
80+
source = "../waf"
81+
82+
name = local.name
83+
tags = local.merged_tags
84+
85+
# WAF configuration with sensible defaults
86+
rate_limit_requests_per_5_minutes = var.waf_rate_limit_requests_per_5_minutes
87+
api_rate_limit_requests_per_5_minutes = var.waf_api_rate_limit_requests_per_5_minutes
88+
geo_restriction_countries = var.waf_geo_restriction_countries
89+
enable_logging = var.waf_enable_logging
90+
log_retention_days = var.waf_log_retention_days
91+
}

deployment/terraform/modules/aws/onyx/variables.tf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,34 @@ variable "redis_auth_token" {
8686
default = null
8787
sensitive = true
8888
}
89+
90+
# WAF Configuration Variables
91+
variable "waf_rate_limit_requests_per_5_minutes" {
92+
type = number
93+
description = "Rate limit for requests per 5 minutes per IP address"
94+
default = 2000
95+
}
96+
97+
variable "waf_api_rate_limit_requests_per_5_minutes" {
98+
type = number
99+
description = "Rate limit for API requests per 5 minutes per IP address"
100+
default = 1000
101+
}
102+
103+
variable "waf_geo_restriction_countries" {
104+
type = list(string)
105+
description = "List of country codes to block. Leave empty to disable geo restrictions"
106+
default = []
107+
}
108+
109+
variable "waf_enable_logging" {
110+
type = bool
111+
description = "Enable WAF logging to S3"
112+
default = true
113+
}
114+
115+
variable "waf_log_retention_days" {
116+
type = number
117+
description = "Number of days to retain WAF logs"
118+
default = 90
119+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
locals {
2+
name = var.name
3+
tags = var.tags
4+
}
5+
6+
# AWS WAFv2 Web ACL
7+
resource "aws_wafv2_web_acl" "main" {
8+
name = "${local.name}-web-acl"
9+
description = "WAF Web ACL for ${local.name}"
10+
scope = "REGIONAL"
11+
12+
default_action {
13+
allow {}
14+
}
15+
16+
# AWS Managed Rules - Core Rule Set
17+
rule {
18+
name = "AWSManagedRulesCommonRuleSet"
19+
priority = 1
20+
21+
override_action {
22+
none {}
23+
}
24+
25+
statement {
26+
managed_rule_group_statement {
27+
name = "AWSManagedRulesCommonRuleSet"
28+
vendor_name = "AWS"
29+
}
30+
}
31+
32+
visibility_config {
33+
cloudwatch_metrics_enabled = true
34+
metric_name = "AWSManagedRulesCommonRuleSetMetric"
35+
sampled_requests_enabled = true
36+
}
37+
}
38+
39+
# AWS Managed Rules - Known Bad Inputs
40+
rule {
41+
name = "AWSManagedRulesKnownBadInputsRuleSet"
42+
priority = 2
43+
44+
override_action {
45+
none {}
46+
}
47+
48+
statement {
49+
managed_rule_group_statement {
50+
name = "AWSManagedRulesKnownBadInputsRuleSet"
51+
vendor_name = "AWS"
52+
}
53+
}
54+
55+
visibility_config {
56+
cloudwatch_metrics_enabled = true
57+
metric_name = "AWSManagedRulesKnownBadInputsRuleSetMetric"
58+
sampled_requests_enabled = true
59+
}
60+
}
61+
62+
# Rate Limiting Rule
63+
rule {
64+
name = "RateLimitRule"
65+
priority = 3
66+
67+
action {
68+
block {}
69+
}
70+
71+
statement {
72+
rate_based_statement {
73+
limit = var.rate_limit_requests_per_5_minutes
74+
aggregate_key_type = "IP"
75+
}
76+
}
77+
78+
visibility_config {
79+
cloudwatch_metrics_enabled = true
80+
metric_name = "RateLimitRuleMetric"
81+
sampled_requests_enabled = true
82+
}
83+
}
84+
85+
# Geo Restriction (if enabled)
86+
dynamic "rule" {
87+
for_each = length(var.geo_restriction_countries) > 0 ? [1] : []
88+
content {
89+
name = "GeoRestrictionRule"
90+
priority = 4
91+
92+
action {
93+
block {}
94+
}
95+
96+
statement {
97+
geo_match_statement {
98+
country_codes = var.geo_restriction_countries
99+
}
100+
}
101+
102+
visibility_config {
103+
cloudwatch_metrics_enabled = true
104+
metric_name = "GeoRestrictionRuleMetric"
105+
sampled_requests_enabled = true
106+
}
107+
}
108+
}
109+
110+
# IP Rate Limiting for specific paths (API protection)
111+
rule {
112+
name = "APIRateLimitRule"
113+
priority = 5
114+
115+
action {
116+
block {}
117+
}
118+
119+
statement {
120+
rate_based_statement {
121+
limit = var.api_rate_limit_requests_per_5_minutes
122+
aggregate_key_type = "IP"
123+
}
124+
}
125+
126+
visibility_config {
127+
cloudwatch_metrics_enabled = true
128+
metric_name = "APIRateLimitRuleMetric"
129+
sampled_requests_enabled = true
130+
}
131+
}
132+
133+
# SQL Injection Protection
134+
rule {
135+
name = "AWSManagedRulesSQLiRuleSet"
136+
priority = 6
137+
138+
override_action {
139+
none {}
140+
}
141+
142+
statement {
143+
managed_rule_group_statement {
144+
name = "AWSManagedRulesSQLiRuleSet"
145+
vendor_name = "AWS"
146+
}
147+
}
148+
149+
visibility_config {
150+
cloudwatch_metrics_enabled = true
151+
metric_name = "AWSManagedRulesSQLiRuleSetMetric"
152+
sampled_requests_enabled = true
153+
}
154+
}
155+
156+
# XSS Protection
157+
rule {
158+
name = "AWSManagedRulesAnonymousIpList"
159+
priority = 7
160+
161+
override_action {
162+
none {}
163+
}
164+
165+
statement {
166+
managed_rule_group_statement {
167+
name = "AWSManagedRulesAnonymousIpList"
168+
vendor_name = "AWS"
169+
}
170+
}
171+
172+
visibility_config {
173+
cloudwatch_metrics_enabled = true
174+
metric_name = "AWSManagedRulesAnonymousIpListMetric"
175+
sampled_requests_enabled = true
176+
}
177+
}
178+
179+
visibility_config {
180+
cloudwatch_metrics_enabled = true
181+
metric_name = "${local.name}WebACLMetric"
182+
sampled_requests_enabled = true
183+
}
184+
185+
tags = local.tags
186+
}
187+
188+
# WAF Logging Configuration (simplified - just CloudWatch)
189+
resource "aws_cloudwatch_log_group" "waf_logs" {
190+
count = var.enable_logging ? 1 : 0
191+
name = "/aws/waf/${local.name}"
192+
retention_in_days = var.log_retention_days
193+
194+
tags = local.tags
195+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
output "web_acl_arn" {
2+
description = "ARN of the WAF Web ACL"
3+
value = aws_wafv2_web_acl.main.arn
4+
}
5+
6+
output "web_acl_id" {
7+
description = "ID of the WAF Web ACL"
8+
value = aws_wafv2_web_acl.main.id
9+
}
10+
11+
output "web_acl_name" {
12+
description = "Name of the WAF Web ACL"
13+
value = aws_wafv2_web_acl.main.name
14+
}
15+
16+
output "log_group_name" {
17+
description = "Name of the CloudWatch log group for WAF logs"
18+
value = var.enable_logging ? aws_cloudwatch_log_group.waf_logs[0].name : null
19+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
variable "name" {
2+
type = string
3+
description = "Name prefix for WAF resources"
4+
}
5+
6+
variable "tags" {
7+
type = map(string)
8+
description = "Tags to apply to all WAF resources"
9+
default = {}
10+
}
11+
12+
variable "rate_limit_requests_per_5_minutes" {
13+
type = number
14+
description = "Rate limit for requests per 5 minutes per IP address"
15+
default = 2000
16+
}
17+
18+
variable "api_rate_limit_requests_per_5_minutes" {
19+
type = number
20+
description = "Rate limit for API requests per 5 minutes per IP address"
21+
default = 1000
22+
}
23+
24+
variable "geo_restriction_countries" {
25+
type = list(string)
26+
description = "List of country codes to block. Leave empty to disable geo restrictions"
27+
default = []
28+
}
29+
30+
variable "enable_logging" {
31+
type = bool
32+
description = "Enable WAF logging to S3"
33+
default = true
34+
}
35+
36+
variable "log_retention_days" {
37+
type = number
38+
description = "Number of days to retain WAF logs"
39+
default = 90
40+
}

0 commit comments

Comments
 (0)