use actix_web::{HttpRequest, dev::ServiceRequest, middleware::Next, dev::ServiceResponse, error::ErrorTooManyRequests}; use log::{warn, error, info}; use redis::{AsyncCommands, aio::MultiplexedConnection}; use std::time::{SystemTime, UNIX_EPOCH}; use std::collections::HashMap; use tokio::sync::RwLock; use std::sync::Arc; use serde::{Deserialize, Serialize}; /// Конфигурация лимитов запросов #[derive(Debug, Clone)] pub struct RateLimitConfig { /// Максимальное количество запросов в окне времени pub max_requests: u32, /// Окно времени в секундах pub window_seconds: u64, /// Блокировка на количество секунд при превышении лимита pub block_duration_seconds: u64, } impl Default for RateLimitConfig { fn default() -> Self { Self { max_requests: 100, // 100 запросов window_seconds: 60, // в минуту block_duration_seconds: 300, // блокировка на 5 минут } } } /// Конфигурация для разных типов запросов #[derive(Debug, Clone)] pub struct SecurityConfig { /// Общий лимит по IP pub general_rate_limit: RateLimitConfig, /// Лимит для загрузки файлов pub upload_rate_limit: RateLimitConfig, /// Лимит для аутентификации pub auth_rate_limit: RateLimitConfig, /// Максимальный размер тела запроса (байты) pub max_payload_size: usize, /// Таймаут запроса (секунды) pub request_timeout_seconds: u64, /// Максимальная длина пути pub max_path_length: usize, /// Максимальное количество заголовков pub max_headers_count: usize, /// Максимальная длина значения заголовка pub max_header_value_length: usize, } impl Default for SecurityConfig { fn default() -> Self { Self { general_rate_limit: RateLimitConfig::default(), upload_rate_limit: RateLimitConfig { max_requests: 10, // 10 загрузок window_seconds: 300, // в 5 минут block_duration_seconds: 600, // блокировка на 10 минут }, auth_rate_limit: RateLimitConfig { max_requests: 20, // 20 попыток аутентификации window_seconds: 900, // в 15 минут block_duration_seconds: 1800, // блокировка на 30 минут }, max_payload_size: 4000 * 1024 * 1024, // 4000 МБ request_timeout_seconds: 300, // 5 минут max_path_length: 1000, max_headers_count: 50, max_header_value_length: 8192, } } } /// Структура для хранения информации о запросах #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RequestInfo { pub count: u32, pub first_request_time: u64, pub blocked_until: Option, } /// Менеджер безопасности pub struct SecurityManager { pub config: SecurityConfig, redis: MultiplexedConnection, // Локальный кэш для быстрых проверок local_cache: Arc>>, } impl SecurityManager { pub fn new(config: SecurityConfig, redis: MultiplexedConnection) -> Self { Self { config, redis, local_cache: Arc::new(RwLock::new(HashMap::new())), } } /// Получает IP адрес из запроса, учитывая прокси pub fn extract_client_ip(req: &HttpRequest) -> String { // Проверяем заголовки прокси if let Some(forwarded_for) = req.headers().get("x-forwarded-for") { if let Ok(forwarded_str) = forwarded_for.to_str() { if let Some(first_ip) = forwarded_str.split(',').next() { return first_ip.trim().to_string(); } } } if let Some(real_ip) = req.headers().get("x-real-ip") { if let Ok(ip_str) = real_ip.to_str() { return ip_str.to_string(); } } // Fallback к connection info req.connection_info() .realip_remote_addr() .unwrap_or("unknown") .to_string() } /// Проверяет лимиты запросов для IP pub async fn check_rate_limit(&mut self, ip: &str, endpoint_type: &str) -> Result<(), actix_web::Error> { let config = match endpoint_type { "upload" => &self.config.upload_rate_limit, "auth" => &self.config.auth_rate_limit, _ => &self.config.general_rate_limit, }; let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); let redis_key = format!("rate_limit:{}:{}", endpoint_type, ip); // Проверяем локальный кэш { let cache = self.local_cache.read().await; if let Some(info) = cache.get(&redis_key) { if let Some(blocked_until) = info.blocked_until { if current_time < blocked_until { warn!("IP {} blocked until {}", ip, blocked_until); return Err(ErrorTooManyRequests("Rate limit exceeded, IP temporarily blocked")); } } } } // Проверяем в Redis let info_str: Option = self.redis.get(&redis_key).await .map_err(|e| { error!("Redis error in rate limit check: {}", e); actix_web::error::ErrorInternalServerError("Service temporarily unavailable") })?; let mut request_info = if let Some(info_str) = info_str { serde_json::from_str::(&info_str) .unwrap_or_else(|_| RequestInfo { count: 0, first_request_time: current_time, blocked_until: None, }) } else { RequestInfo { count: 0, first_request_time: current_time, blocked_until: None, } }; // Проверяем блокировку if let Some(blocked_until) = request_info.blocked_until { if current_time < blocked_until { warn!("IP {} is blocked until {}", ip, blocked_until); return Err(ErrorTooManyRequests("Rate limit exceeded, IP temporarily blocked")); } else { // Блокировка истекла, сбрасываем request_info.blocked_until = None; request_info.count = 0; request_info.first_request_time = current_time; } } // Проверяем окно времени if current_time - request_info.first_request_time > config.window_seconds { // Новое окно времени, сбрасываем счетчик request_info.count = 0; request_info.first_request_time = current_time; } // Увеличиваем счетчик request_info.count += 1; // Проверяем лимит if request_info.count > config.max_requests { warn!("Rate limit exceeded for IP {}: {} requests in window", ip, request_info.count); // Устанавливаем блокировку request_info.blocked_until = Some(current_time + config.block_duration_seconds); // Сохраняем в Redis let info_str = serde_json::to_string(&request_info).unwrap(); let _: () = self.redis.set_ex(&redis_key, info_str, config.block_duration_seconds).await .map_err(|e| { error!("Redis error saving rate limit: {}", e); actix_web::error::ErrorInternalServerError("Service temporarily unavailable") })?; // Обновляем локальный кэш { let mut cache = self.local_cache.write().await; cache.insert(redis_key, request_info); } return Err(ErrorTooManyRequests("Rate limit exceeded, IP temporarily blocked")); } // Сохраняем обновленную информацию let info_str = serde_json::to_string(&request_info).unwrap(); let _: () = self.redis.set_ex(&redis_key, info_str, config.window_seconds * 2).await .map_err(|e| { error!("Redis error updating rate limit: {}", e); actix_web::error::ErrorInternalServerError("Service temporarily unavailable") })?; let count = request_info.count; // Обновляем локальный кэш { let mut cache = self.local_cache.write().await; cache.insert(redis_key, request_info); } info!("Rate limit check passed for IP {}: {}/{} requests", ip, count, config.max_requests); Ok(()) } /// Проверяет безопасность запроса (размер, заголовки, путь) pub fn validate_request_security(&self, req: &HttpRequest) -> Result<(), actix_web::Error> { // Проверка длины пути let path = req.path(); if path.len() > self.config.max_path_length { warn!("Request path too long: {} chars", path.len()); return Err(actix_web::error::ErrorBadRequest("Request path too long")); } // Проверка количества заголовков if req.headers().len() > self.config.max_headers_count { warn!("Too many headers: {}", req.headers().len()); return Err(actix_web::error::ErrorBadRequest("Too many headers")); } // Проверка длины значений заголовков for (name, value) in req.headers().iter() { if let Ok(value_str) = value.to_str() { if value_str.len() > self.config.max_header_value_length { warn!("Header value too long: {} = {} chars", name, value_str.len()); return Err(actix_web::error::ErrorBadRequest("Header value too long")); } } } // Проверка на подозрительные символы в пути if path.contains("..") || path.contains('\0') || path.contains('\r') || path.contains('\n') { warn!("Suspicious characters in path: {}", path); return Err(actix_web::error::ErrorBadRequest("Invalid characters in path")); } Ok(()) } /// Проверяет подозрительные паттерны в пути pub fn check_suspicious_patterns(&self, path: &str) -> bool { let suspicious_patterns = [ "/admin", "/wp-admin", "/phpmyadmin", "/.env", "/config", "/.git", "/backup", "/db", "/sql", "/.well-known/acme-challenge", "/xmlrpc.php", "/wp-login.php", "/wp-config.php", "script>", " 3600 { to_remove.push(key.clone()); } } for key in to_remove { cache.remove(&key); } info!("Cleaned {} old entries from security cache", cache.len()); } } /// Middleware для проверки безопасности pub async fn security_middleware( req: ServiceRequest, next: Next, ) -> Result, actix_web::Error> { let path = req.path().to_string(); let method = req.method().to_string(); // Быстрая проверка на известные атаки if path.contains("..") || path.contains('\0') || path.len() > 1000 { warn!("Blocked suspicious request: {} {}", method, path); return Err(actix_web::error::ErrorBadRequest("Invalid request")); } // Проверка на bot patterns if let Some(user_agent) = req.headers().get("user-agent") { if let Ok(ua_str) = user_agent.to_str() { let ua_lower = ua_str.to_lowercase(); if ua_lower.contains("bot") || ua_lower.contains("crawler") || ua_lower.contains("spider") { // Для ботов применяем более строгие лимиты info!("Bot detected: {}", ua_str); } } } let res = next.call(req).await?; Ok(res) }