Skip to content

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_bancấ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_keyBộ đếm theoGhi chú
ALLMột bộ đếm chung cho mọi request khớp ruleGiới hạn tổng, không phân biệt client
IPMỗi IP client một bộ đếmPhổ biến nhất; thất bại khi có proxy/NAT chung
XFF_IPIP đầu tiên trong X-Forwarded-ForDùng khi có proxy đứng trước
HTTP_HEADERMỗi giá trị header một bộ đếm (cắt còn 128 byte)Ví dụ theo API key trong header
HTTP_COOKIEMỗi giá trị cookie (cắt 128 byte)Theo session
HTTP_PATHTheo URL path (cắt 128 byte)Bảo vệ endpoint cụ thể
SNITheo TLS Server Name Indication (128 byte)Theo hostname
REGION_CODETheo quốc gia originGiới hạn theo vùng
TLS_JA3_FINGERPRINT / TLS_JA4_FINGERPRINTTheo chữ ký TLS clientChống bot xoay IP
USER_IPIP lấy từ userIpRequestHeadersIP 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 IP khi 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ọn JA4 → 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ới throttle: 1–1,000,000. Với rate_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, 3600 giâ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 status 403, 404, 429 (khuyến nghị 429 Too Many Requests), hoặc 502; hoặc redirect (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õ:

  1. "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.

  2. "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 động khi ướ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ỉ ALLIP key được hỗ trợ đầy đủ; HTTP_HEADERHTTP_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ế:

yaml
- 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