# 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 } } }