🔒 Implement comprehensive security and DDoS protection

### Security Features:
- **Rate Limiting**: Redis-based IP tracking with configurable limits
  - General: 100 requests/minute (5min block)
  - Upload: 10 requests/5min (10min block)
  - Auth: 20 requests/15min (30min block)
- **Request Validation**: Path length, header count, suspicious patterns
- **Attack Detection**: Admin paths, script injections, bot patterns
- **Enhanced JWT**: Format validation, length checks, character filtering
- **IP Tracking**: X-Forwarded-For and X-Real-IP support

### Security Headers:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- X-XSS-Protection: 1; mode=block
- Content-Security-Policy with strict rules
- Strict-Transport-Security with includeSubDomains

### CORS Hardening:
- Limited to specific domains: discours.io, new.discours.io
- Restricted methods: GET, POST, OPTIONS only
- Essential headers only

### Infrastructure:
- Security middleware for all requests
- Local cache + Redis for performance
- Comprehensive logging and monitoring
- Progressive blocking for repeat offenders

### Documentation:
- Complete security guide (docs/security.md)
- Configuration examples
- Incident response procedures
- Monitoring recommendations

Version bump to 0.6.0 for major security enhancement.
This commit is contained in:
2025-09-02 11:40:43 +03:00
parent d3bee5144f
commit 82668768d0
14 changed files with 803 additions and 124 deletions

View File

@@ -3,19 +3,21 @@ mod auth;
mod handlers;
mod lookup;
mod s3_utils;
mod security;
mod thumbnail;
use actix_cors::Cors;
use actix_web::{
App, HttpServer,
http::header::{self, HeaderName},
middleware::Logger,
http::header,
middleware::{Logger, DefaultHeaders},
web,
};
use app_state::AppState;
use security::{SecurityConfig, security_middleware};
use handlers::universal_handler;
use log::warn;
use log::{warn, info};
use std::env;
use tokio::task::spawn_blocking;
@@ -37,27 +39,47 @@ async fn main() -> std::io::Result<()> {
});
});
// Конфигурация безопасности
let security_config = SecurityConfig::default();
info!("Security config: max_payload={} MB, upload_rate_limit={}/{}s",
security_config.max_payload_size / (1024 * 1024),
security_config.upload_rate_limit.max_requests,
security_config.upload_rate_limit.window_seconds);
HttpServer::new(move || {
// Настройка CORS middleware
// Настройка CORS middleware - ограничиваем в продакшене
let cors = Cors::default()
.allow_any_origin() // TODO: ограничить конкретными доменами в продакшене
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])
.allowed_origin("https://discours.io")
.allowed_origin("https://new.discours.io")
.allowed_origin("https://testing.discours.io")
.allowed_origin("https://testing3.discours.io")
.allowed_origin("http://localhost:3000") // для разработки
.allowed_methods(vec!["GET", "POST", "OPTIONS"])
.allowed_headers(vec![
header::DNT,
header::USER_AGENT,
HeaderName::from_static("x-requested-with"),
header::IF_MODIFIED_SINCE,
header::CACHE_CONTROL,
header::CONTENT_TYPE,
header::RANGE,
header::AUTHORIZATION,
header::IF_NONE_MATCH,
header::CACHE_CONTROL,
])
.expose_headers(vec![header::CONTENT_LENGTH, header::CONTENT_RANGE])
.expose_headers(vec![header::CONTENT_LENGTH, header::ETAG])
.supports_credentials()
.max_age(1728000); // 20 дней
.max_age(86400); // 1 день вместо 20
// Заголовки безопасности
let security_headers = DefaultHeaders::new()
.add(("X-Content-Type-Options", "nosniff"))
.add(("X-Frame-Options", "DENY"))
.add(("X-XSS-Protection", "1; mode=block"))
.add(("Referrer-Policy", "strict-origin-when-cross-origin"))
.add(("Content-Security-Policy", "default-src 'self'; img-src 'self' data: https:; object-src 'none';"))
.add(("Strict-Transport-Security", "max-age=31536000; includeSubDomains"));
App::new()
.app_data(web::Data::new(app_state.clone()))
.app_data(web::PayloadConfig::new(security_config.max_payload_size))
.app_data(web::JsonConfig::default().limit(1024 * 1024)) // 1MB для JSON
.wrap(actix_web::middleware::from_fn(security_middleware))
.wrap(security_headers)
.wrap(cors)
.wrap(Logger::default())
.default_service(web::to(universal_handler))