feat(quotesdb): add Cloudflare WAF rate limiting rules via OpenTofu
Adds infra/rate-limits.tf with a cloudflare_ruleset (phase: http_ratelimit) implementing per-IP rate limits on all mutating API endpoints: - PUT /api/quotes: 5 requests per 10 minutes (quote creation) - POST /api/quotes/:id/report: 3 requests per hour (abuse reports) - POST /api/quotes/🆔 10 requests per minute (quote updates) - DELETE /api/quotes/🆔 10 requests per minute (quote deletes) The report rule is ordered before the general update rule to ensure the more-specific /report path matches before the broader /api/quotes/:id pattern. Documents the approach, plan requirements, and layered protection rationale in docs/ARCHITECTURE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>quotesdb
parent
ab76d35bd5
commit
db71399b2f
@ -0,0 +1,69 @@
|
||||
# Cloudflare WAF rate limiting rules for the quotesdb API.
|
||||
# Uses the http_ratelimit phase of cloudflare_ruleset to enforce per-IP request caps
|
||||
# on all mutating endpoints. Rules are evaluated top-to-bottom; first match wins.
|
||||
# The report rule must appear before the general update rule to prevent the broader
|
||||
# /api/quotes/* pattern from matching /api/quotes/*/report first.
|
||||
resource "cloudflare_ruleset" "api_rate_limits" {
|
||||
# Scoped to the elijah.run zone that hosts quotes.elijah.run.
|
||||
zone_id = var.cloudflare_zone_id
|
||||
name = "quotesdb API rate limits"
|
||||
description = "Per-IP rate limiting for mutating quotesdb API endpoints"
|
||||
kind = "zone"
|
||||
phase = "http_ratelimit"
|
||||
|
||||
rules {
|
||||
# Limit quote creation via PUT /api/quotes: 5 requests per IP per 10 minutes.
|
||||
# PUT is the create verb per the quotesdb API design.
|
||||
description = "Limit PUT /api/quotes to 5 per IP per 10 minutes"
|
||||
expression = "(http.request.method eq \"PUT\" and http.request.uri.path eq \"/api/quotes\")"
|
||||
action = "block"
|
||||
ratelimit {
|
||||
characteristics = ["ip.src"]
|
||||
period = 600
|
||||
requests_per_period = 5
|
||||
mitigation_timeout = 600
|
||||
}
|
||||
}
|
||||
|
||||
rules {
|
||||
# Limit report submissions: 3 POST /api/quotes/:id/report per IP per hour.
|
||||
# This rule must precede the general update rule below so the more-specific
|
||||
# /report path is matched first before the broader /api/quotes/* pattern.
|
||||
description = "Limit POST /api/quotes/*/report to 3 per IP per hour"
|
||||
expression = "(http.request.method eq \"POST\" and http.request.uri.path matches \"/api/quotes/[^/]+/report$\")"
|
||||
action = "block"
|
||||
ratelimit {
|
||||
characteristics = ["ip.src"]
|
||||
period = 3600
|
||||
requests_per_period = 3
|
||||
mitigation_timeout = 3600
|
||||
}
|
||||
}
|
||||
|
||||
rules {
|
||||
# Limit quote updates: 10 POST /api/quotes/:id per IP per minute.
|
||||
# Excludes the /report sub-path (handled by the rule above).
|
||||
description = "Limit POST /api/quotes/:id to 10 per IP per minute"
|
||||
expression = "(http.request.method eq \"POST\" and http.request.uri.path matches \"/api/quotes/[^/]+$\")"
|
||||
action = "block"
|
||||
ratelimit {
|
||||
characteristics = ["ip.src"]
|
||||
period = 60
|
||||
requests_per_period = 10
|
||||
mitigation_timeout = 60
|
||||
}
|
||||
}
|
||||
|
||||
rules {
|
||||
# Limit quote deletes: 10 DELETE /api/quotes/:id per IP per minute.
|
||||
description = "Limit DELETE /api/quotes/:id to 10 per IP per minute"
|
||||
expression = "(http.request.method eq \"DELETE\" and http.request.uri.path matches \"/api/quotes/[^/]+$\")"
|
||||
action = "block"
|
||||
ratelimit {
|
||||
characteristics = ["ip.src"]
|
||||
period = 60
|
||||
requests_per_period = 10
|
||||
mitigation_timeout = 60
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue