From dca8d067f38b3db0c41353ac2751848ce8039264 Mon Sep 17 00:00:00 2001 From: nineap Date: Sat, 11 Jul 2026 03:30:52 +0700 Subject: [PATCH] =?UTF-8?q?Fix:=20rate-limiter=20(tower=5Fgovernor)=20?= =?UTF-8?q?=D1=81=D1=87=D0=B8=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=81=D1=8C?= =?UTF-8?q?=20=D1=82=D1=80=D0=B0=D1=84=D0=B8=D0=BA=20=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Дефолтный PeerIpKeyExtractor брал IP из TCP-peer'а, а не заголовков — а nginx проксирует локально, так что peer всегда 127.0.0.1. В итоге лимит "3 запроса/5с" на /auth и "10/100мс" на остальной API/v1 был не per-visitor, а глобальным на весь прод: несколько попыток логина от кого угодно — и все остальные посетители ловили 429 на /auth (это и мешало зайти в свежесозданную админку — 401 на пробное неверное дело быстро съедал бюджет, дальше только 429). Добавлен SmartIpKeyExtractor (x-forwarded-for/x-real-ip/forwarded, именно то, что реально шлёт nginx) в оба GovernorConfigBuilder. --- src/api.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/api.rs b/src/api.rs index 87c2e8d..57430c1 100644 --- a/src/api.rs +++ b/src/api.rs @@ -17,7 +17,9 @@ use axum::{ use metrics_exporter_prometheus::{Matcher, PrometheusBuilder}; use serde_json::json; use std::{sync::Arc, time::Instant}; -use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer}; +use tower_governor::{ + governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, GovernorLayer, +}; use tower_http::{ cors::CorsLayer, services::{ServeDir, ServeFile}, @@ -111,8 +113,15 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router { let static_path = std::path::PathBuf::from(frontend_dir); let index_path = static_path.join("index.html"); + // SmartIpKeyExtractor читает x-forwarded-for/x-real-ip/forwarded (в этом + // порядке), падая на peer IP только если их нет. Без этого — дефолтный + // PeerIpKeyExtractor видел бы просто 127.0.0.1 (nginx проксирует локально) + // ОДИН И ТОТ ЖЕ для абсолютно всех посетителей сразу, и лимит превращался + // бы в глобальный на весь сервис, а не per-visitor (см. incident: 3 попытки + // логина от кого угодно — и /auth 429 для всех остальных до истечения окна). let gov_conf = Arc::new( GovernorConfigBuilder::default() + .key_extractor(SmartIpKeyExtractor) .per_millisecond(100) .burst_size(10) .finish() @@ -121,6 +130,7 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router { let seed_limit_conf = Arc::new( GovernorConfigBuilder::default() + .key_extractor(SmartIpKeyExtractor) .per_millisecond(5000) .burst_size(3) .finish()