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>main
parent
5d2cdcae8e
commit
d9099c5585
@ -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