Fix: rate-limiter (tower_governor) считал весь трафик одним клиентом
Дефолтный 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.
This commit is contained in:
+11
-1
@@ -17,7 +17,9 @@ use axum::{
|
|||||||
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder};
|
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer};
|
use tower_governor::{
|
||||||
|
governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, GovernorLayer,
|
||||||
|
};
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
cors::CorsLayer,
|
cors::CorsLayer,
|
||||||
services::{ServeDir, ServeFile},
|
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 static_path = std::path::PathBuf::from(frontend_dir);
|
||||||
let index_path = static_path.join("index.html");
|
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(
|
let gov_conf = Arc::new(
|
||||||
GovernorConfigBuilder::default()
|
GovernorConfigBuilder::default()
|
||||||
|
.key_extractor(SmartIpKeyExtractor)
|
||||||
.per_millisecond(100)
|
.per_millisecond(100)
|
||||||
.burst_size(10)
|
.burst_size(10)
|
||||||
.finish()
|
.finish()
|
||||||
@@ -121,6 +130,7 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router {
|
|||||||
|
|
||||||
let seed_limit_conf = Arc::new(
|
let seed_limit_conf = Arc::new(
|
||||||
GovernorConfigBuilder::default()
|
GovernorConfigBuilder::default()
|
||||||
|
.key_extractor(SmartIpKeyExtractor)
|
||||||
.per_millisecond(5000)
|
.per_millisecond(5000)
|
||||||
.burst_size(3)
|
.burst_size(3)
|
||||||
.finish()
|
.finish()
|
||||||
|
|||||||
Reference in New Issue
Block a user