Rate Limiting: Throttle & Rate-Based Ban
Why this matters in production
Rate limiting là phòng thủ chính chống L7 abuse: credential stuffing, scraping, brute-force, application-layer DDoS, API abuse. Nhưng nó là tính năng bị cấu hình sai nhiều nhất, vì phần lớn kỹ sư mang theo mental model từ rate limiter in-process (như token bucket trong Redis) — nơi bộ đếm chính xác tuyệt đối và toàn cục.
Cloud Armor rate limiting không như vậy. Nó là bộ đếm phân tán trên hàng nghìn GFE, xấp xỉ, và thực thi độc lập theo từng region. Không hiểu điều này, bạn sẽ đặt threshold tưởng là "1000 req/phút toàn cục" nhưng thực tế cho phép bội số của con số đó lọt qua, rồi kết luận "rate limit không hoạt động". File này xây dựng mental model chính xác về bộ đếm phân tán.
Internal model — cơ chế vận hành
Hai action: throttle vs rate-based ban
Cloud Armor có hai chế độ rate limiting, khác nhau về cái gì xảy ra khi vượt ngưỡng:
throttle — giới hạn tốc độ. Khi một client vượt ngưỡng, các request vượt bị từ chối (theo exceed_action), nhưng ngay khi tốc độ giảm xuống dưới ngưỡng, client lại được phục vụ bình thường. Không có "hình phạt" kéo dài. Phù hợp khi bạn muốn bảo vệ backend khỏi quá tải mà không trừng phạt client — ví dụ smooth traffic của một API.
rate_based_ban — cấm nguồn. Khi client vượt ngưỡng, Cloud Armor chặn toàn bộ request từ nguồn đó trong một khoảng thời gian (ban_duration_sec), kể cả khi nó đã ngừng vượt ngưỡng. Đây là "phạt và cấm cửa" — phù hợp chống abuse rõ ràng (brute-force, scraping) nơi bạn muốn đẩy attacker ra hẳn một khoảng thời gian.
Mental model: throttle là "van giảm áp" (mở lại ngay khi áp giảm); rate-based ban là "cầu dao" (ngắt và giữ ngắt một khoảng thời gian). Chọn sai làm hoặc quá nhẹ (throttle với attacker kiên trì) hoặc quá nặng (ban với traffic burst hợp lệ).
Bộ đếm per-key: enforce_on_key
Cốt lõi của rate limiting là câu hỏi: "một client" được định nghĩa thế nào? Cloud Armor duy trì một bộ đếm riêng cho mỗi giá trị key, và enforce_on_key quyết định key là gì:
enforce_on_key | Bộ đếm theo | Ghi chú |
|---|---|---|
ALL | Một bộ đếm chung cho mọi request khớp rule | Giới hạn tổng, không phân biệt client |
IP | Mỗi IP client một bộ đếm | Phổ biến nhất; thất bại khi có proxy/NAT chung |
XFF_IP | IP đầu tiên trong X-Forwarded-For | Dùng khi có proxy đứng trước |
HTTP_HEADER | Mỗi giá trị header một bộ đếm (cắt còn 128 byte) | Ví dụ theo API key trong header |
HTTP_COOKIE | Mỗi giá trị cookie (cắt 128 byte) | Theo session |
HTTP_PATH | Theo URL path (cắt 128 byte) | Bảo vệ endpoint cụ thể |
SNI | Theo TLS Server Name Indication (128 byte) | Theo hostname |
REGION_CODE | Theo quốc gia origin | Giới hạn theo vùng |
TLS_JA3_FINGERPRINT / TLS_JA4_FINGERPRINT | Theo chữ ký TLS client | Chống bot xoay IP |
USER_IP | IP lấy từ userIpRequestHeaders | IP thật của user sau proxy |
Nhiều key cùng lúc: bạn có thể dùng enforce_on_key_configs để kết hợp — tối đa ba key, trong đó nhiều HTTP_HEADER/HTTP_COOKIE nhưng chỉ một của mỗi loại key khác. Ví dụ rate limit theo tổ hợp (IP + API key header) để một API key không thể lạm dụng dù đổi IP.
Đây là quyết định thiết kế quan trọng nhất của một rate-limit rule. Chọn
IPkhi client đứng sau CGNAT/proxy chung → bạn gộp hàng nghìn user thật thành một "client" và ban nhầm tất cả. Chọn key quá rộng (ALL) → không phân biệt được attacker với user. ChọnJA4→ chống được bot xoay IP mà IP-based không làm được. Key định nghĩa ai bị đếm, và nó quyết định rate limit có ý nghĩa hay không.
Threshold và interval
Ngưỡng được định nghĩa bởi cặp:
rate_limit_threshold_count— số request cho phép mỗi key trong một interval. Vớithrottle: 1–1,000,000. Vớirate_based_ban: 1–10,000.interval_sec— cửa sổ thời gian. Giá trị hợp lệ rời rạc:10, 30, 60, 120, 180, 240, 300, 600, 900, 1200, 1800, 2700, 3600giây (không phải giá trị tùy ý).
Khi vượt ngưỡng:
conform_action— luôn làallow(khi dưới ngưỡng).exceed_action— khi vượt:deny(STATUS)với status403, 404, 429(khuyến nghị 429 Too Many Requests), hoặc502; hoặcredirect(GOOGLE_RECAPTCHA/EXTERNAL_302).
Với rate_based_ban, thêm:
ban_duration_sec— thời gian cấm sau khi vượt (giá trị rời rạc:60…3600).ban_threshold_count+ban_threshold_interval_sec(tùy chọn) — ngưỡng phụ để tránh ban nhầm: chỉ ban khi vượt cả ngưỡng chính lẫn ngưỡng ban trong cửa sổ ban. Cơ chế hai tầng này cho phép throttle nhẹ trước, chỉ ban khi hành vi thực sự kéo dài.
Redirect tới reCAPTCHA — rate limit "mềm"
exceed_action = redirect(GOOGLE_RECAPTCHA) đặc biệt mạnh: thay vì chặn cứng, client vượt ngưỡng bị chuyển tới thử thách reCAPTCHA. Người thật giải được và tiếp tục; bot thì không. Đây là cách phân biệt "user hợp lệ đang thao tác nhanh" với "bot" mà không gây lỗi 429 cho người thật — chuyển bài toán từ "đếm request" sang "chứng minh là người".
Bản chất phân tán: vì sao rate limit là XẤP XỈ và PER-REGION
Đây là phần bị hiểu sai nhiều nhất, và được tài liệu Google nói rõ:
"Thresholds for throttling and rate-based bans are enforced independently in each Google Cloud region." Bộ đếm được duy trì theo từng region, không phải một bộ đếm toàn cầu. Nếu attacker đánh từ nhiều region, mỗi region áp ngưỡng riêng. Tổng traffic lọt qua có thể là
threshold × số_region."Rate limits are approximate and might not be strictly accurate." Bộ đếm là xấp xỉ vì phải đồng bộ trạng thái giữa nhiều GFE trong một region với độ trễ. Nó dùng để giảm abuse ở quy mô, không phải để enforce quota chính xác kiểu billing.
Hệ quả thiết kế trực tiếp:
- Đừng đặt threshold sát ngưỡng chịu tải backend kỳ vọng nó chặn chính xác. Đặt threshold với biên an toàn, hiểu rằng traffic thực lọt qua có thể cao hơn con số cấu hình.
- Nếu cần quota chính xác (ví dụ "user X được đúng 1000 call/ngày"), rate limit của Cloud Armor không phải công cụ đúng — dùng API management/quota layer ở application (ví dụ Apigee). Cloud Armor là abuse mitigation ở edge, không phải metering chính xác.
- Với traffic phân bố nhiều region, cân nhắc con số
threshold × số region hoạt độngkhi ước lượng ngưỡng tổng.
Kết hợp nhiều key: enforce_on_key_configs
Với enforce_on_key_configs, bạn định nghĩa bộ đếm theo tổ hợp nhiều thuộc tính (tối đa 3, trong đó chỉ một của mỗi loại ngoài HTTP_HEADER/HTTP_COOKIE). Cơ chế: Cloud Armor tạo một khóa ghép từ các thành phần, và bộ đếm theo giá trị ghép đó. Ví dụ tổ hợp (IP, HTTP_HEADER: x-api-key):
- Một API key gọi từ nhiều IP → mỗi cặp (IP, key) là một bộ đếm riêng → key đó không bị giới hạn tổng nếu trải nhiều IP.
- Ngược lại, tổ hợp giúp phân biệt tinh hơn: hai user khác nhau sau cùng một NAT (cùng IP) nhưng khác API key sẽ có bộ đếm riêng, tránh ban nhầm lẫn nhau.
Chọn tổ hợp key là bài toán bạn muốn cô lập abuse theo chiều nào. Ghép quá nhiều chiều → khóa quá "hẹp", attacker chỉ cần đổi một chiều (thêm IP) là thoát; ghép quá ít → gộp nhầm user. Không có cấu hình đúng phổ quát — nó phụ thuộc mô hình client của app.
Cơ chế bộ đếm: vì sao xấp xỉ
Đào sâu hơn phần "xấp xỉ": trong mỗi region có nhiều GFE cùng phục vụ, mỗi GFE giữ view cục bộ về bộ đếm của từng key và đồng bộ định kỳ với nhau. Giữa hai lần đồng bộ, một key có thể vượt ngưỡng cục bộ trên nhiều GFE trước khi trạng thái hội tụ. Đây là đánh đổi cố hữu của distributed counting: đồng bộ tức thời tuyệt đối sẽ giết throughput ở scale edge, nên Google chọn nhất quán eventual — đủ tốt để chặn abuse quy mô, nhưng không phải quota chính xác từng request.
Kết hợp với việc bộ đếm độc lập theo region, con số thực tế lọt qua = (sai số đồng bộ trong region) × (số region). Đây là lý do threshold nên đặt ở mức "rõ ràng bất thường" hơn là "vừa đủ ngưỡng chịu tải" — bạn đang dùng một công cụ thống kê, không phải một cổng đếm chính xác.
Constraints, trade-offs & failure modes
Failure mode — IP key sau CGNAT/proxy: rate limit enforce_on_key = IP khi client đứng sau carrier-grade NAT hoặc corporate proxy → hàng nghìn user thật xuất hiện như một IP → vượt ngưỡng → ban nhầm toàn bộ. Gốc rễ: hiểu sai "IP = một user". Với môi trường có NAT/proxy, dùng XFF_IP/USER_IP (IP thật) hoặc key khác (cookie/header/JA4).
Failure mode — threshold quá nhạy với burst hợp lệ: app có hành vi burst bình thường (ví dụ trang load nhiều asset, hoặc client retry) → chạm ngưỡng → user thật bị 429/ban. Dùng ban_threshold hai tầng, hoặc throttle thay vì ban, hoặc redirect reCAPTCHA thay vì deny cứng.
Kỳ vọng sai về tính chính xác toàn cục: đặt "1000/phút" và ngạc nhiên khi thấy 4000/phút lọt qua vì traffic trải trên 4 region. Đây không phải bug — đó là bản chất per-region. Đọc enforce_on_key outcome trong log để hiểu bộ đếm thực tế.
External proxy Network Load Balancer: với loại LB này, chỉ ALL và IP key được hỗ trợ đầy đủ; HTTP_HEADER và HTTP_COOKIE bị diễn giải thành ALL. Cấu hình key tinh vi ở đây sẽ âm thầm thoái hóa thành ALL.
Interval là giá trị rời rạc — không đặt được interval_sec = 45; phải chọn từ tập cho phép. Thiết kế threshold quanh các mốc này.
Trade-off throttle vs ban: throttle bảo vệ backend nhưng không đẩy attacker đi (nó cứ thử lại ngay khi tốc độ giảm); ban đẩy attacker đi nhưng rủi ro trừng phạt burst hợp lệ. Nhiều thiết kế tốt xếp tầng: throttle ở ngưỡng thấp (bảo vệ backend) → rate-based-ban ở ngưỡng cao (đuổi abuser rõ ràng), tận dụng first-match-wins theo priority.
Production pattern: chống credential stuffing trên /login
Endpoint /login bị brute-force từ botnet. Thiết kế theo cơ chế:
- priority: 900
match: request.path.startsWith('/login') && request.method == 'POST'
action: rate_based_ban
rate_limit_options:
enforce_on_key: IP # hoặc XFF_IP nếu có proxy
rate_limit_threshold: { count: 5, interval_sec: 60 }
exceed_action: deny(429)
ban_duration_sec: 600
ban_threshold: { count: 20, interval_sec: 120 } # hai tầng: chỉ ban khi kiên trìBài học cơ chế: ban_threshold hai tầng tránh ban nhầm người dùng gõ sai mật khẩu vài lần (throttle nhẹ trước), nhưng ban 10 phút với IP thử 20 lần trong 2 phút. Với botnet xoay IP, thêm rule song song enforce_on_key = TLS_JA4_FINGERPRINT để bắt theo chữ ký client thay vì IP.
Official references
- Rate limiting overview — throttle vs ban, enforce_on_key, threshold, bản chất per-region/xấp xỉ
- Configure rate limiting —
enforce_on_key_configs, ban_threshold, ví dụ gcloud - Cloud Armor rules language reference