# ============================================================================= # Gateway Nginx Configuration for mc.exbytestudios.com # # Архитектура: # Internet -> Gateway Nginx (этот сервер) -> Docker Server (192.168.200.10:8443) # # Gateway: SSL termination, DDoS protection, Rate limiting, Security headers # Docker: Internal nginx -> FastAPI + Guacamole # ============================================================================= # ============================================================================= # CORS Allowed Origins # 🔒 ВАЖНО: Добавьте сюда только доверенные домены! # ============================================================================= map $http_origin $cors_origin { default ""; "~^https://mc\.exbytestudios\.com$" $http_origin; "~^https://test\.exbytestudios\.com$" $http_origin; "~^http://localhost:5173$" $http_origin; "~^http://127\.0\.0\.1:5173$" $http_origin; } # Rate limiting для защиты от DDoS limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; limit_req_zone $binary_remote_addr zone=guacamole:10m rate=50r/s; # Upstream для Docker сервера (внутренняя сеть) upstream docker_server { server 192.168.200.10:8443; keepalive 32; keepalive_requests 100; keepalive_timeout 60s; } # ============================================================================= # HTTP Server - Redirect to HTTPS # ============================================================================= server { listen 80; listen [::]:80; server_name mc.exbytestudios.com; # Let's Encrypt ACME challenge location /.well-known/acme-challenge/ { root /var/www/html; } # Redirect all other traffic to HTTPS location / { return 301 https://$server_name$request_uri; } } # ============================================================================= # HTTPS Server - Main Gateway # ============================================================================= server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name mc.exbytestudios.com; # SSL Configuration # ⚠️ Эти пути будут автоматически настроены certbot # Если certbot не используется, замените на свои сертификаты ssl_certificate /etc/letsencrypt/live/mc.exbytestudios.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mc.exbytestudios.com/privkey.pem; # Modern SSL configuration (Mozilla Intermediate) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/mc.exbytestudios.com/chain.pem; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; # Security Headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Logging access_log /var/log/nginx/mc.exbytestudios.com.access.log; error_log /var/log/nginx/mc.exbytestudios.com.error.log warn; # General settings client_max_body_size 100M; client_body_timeout 120s; client_header_timeout 120s; keepalive_timeout 65s; # ========================================================================= # Root - Redirect to API docs # ========================================================================= location = / { return 302 /api/docs; } # ========================================================================= # API Endpoints - Rate limiting # ========================================================================= location /api/ { limit_req zone=api burst=20 nodelay; # ✅ CORS Headers для /api/machines/saved и других /api/* endpoints # Используем $cors_origin из map для проверки разрешенных доменов add_header 'Access-Control-Allow-Origin' '$cors_origin' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always; add_header 'Access-Control-Allow-Credentials' 'true' always; # Handle preflight requests if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '$cors_origin' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } proxy_pass http://docker_server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; # ✅ КРИТИЧНО: Скрываем CORS заголовки от backend чтобы избежать дубликатов proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Allow-Credentials; # Timeouts proxy_connect_timeout 30s; proxy_send_timeout 120s; proxy_read_timeout 120s; # Buffering proxy_buffering on; proxy_buffer_size 8k; proxy_buffers 8 8k; proxy_busy_buffers_size 16k; # Cache control add_header Cache-Control "no-cache, no-store, must-revalidate" always; add_header Pragma "no-cache" always; add_header Expires "0" always; } # ========================================================================= # WebSocket Notifications - специальная обработка для WebSocket # КРИТИЧНО: Длинные таймауты и отключение буферизации # ========================================================================= location /ws/ { # Легкий rate limiting для WebSocket (меньше чем для API) limit_req zone=api burst=5 nodelay; # ✅ CORS Headers для WebSocket подключений add_header 'Access-Control-Allow-Origin' '$cors_origin' always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions, Sec-WebSocket-Key, Sec-WebSocket-Version' always; add_header 'Access-Control-Allow-Credentials' 'true' always; # Handle preflight requests (хотя для WebSocket обычно не нужны) if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '$cors_origin' always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions, Sec-WebSocket-Key, Sec-WebSocket-Version' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } proxy_pass http://docker_server; proxy_http_version 1.1; # ✅ КРИТИЧНО: WebSocket upgrade headers proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # Standard proxy headers proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; # ✅ КРИТИЧНО: Скрываем CORS заголовки от backend proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Allow-Credentials; # ✅ КРИТИЧНО: Длинные таймауты для WebSocket (до 2 часов) proxy_connect_timeout 60s; proxy_send_timeout 7200s; proxy_read_timeout 7200s; # ✅ КРИТИЧНО: Отключаем буферизацию для WebSocket proxy_buffering off; proxy_request_buffering off; # Cache control add_header Cache-Control "no-cache, no-store, must-revalidate" always; } # ========================================================================= # Guacamole Web Application - Rate limiting # ========================================================================= location /guacamole/ { limit_req zone=guacamole burst=10 nodelay; proxy_pass http://docker_server/guacamole/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket support - long timeouts for remote sessions proxy_read_timeout 7200s; proxy_send_timeout 7200s; # Disable buffering for WebSocket proxy_buffering off; proxy_request_buffering off; # Allow iframe embedding for Guacamole client (desktop/electron apps) proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; # Cache control add_header Cache-Control "no-cache, no-store, must-revalidate" always; } # Guacamole WebSocket Tunnel location /guacamole/websocket-tunnel { limit_req zone=guacamole burst=5 nodelay; proxy_pass http://docker_server/guacamole/websocket-tunnel; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket specific settings proxy_read_timeout 7200s; proxy_send_timeout 7200s; proxy_buffering off; proxy_request_buffering off; # Allow iframe embedding and WebSocket in iframe proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; } # Guacamole Static Assets - Caching location ~ ^/guacamole/(.*\.(js|css|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot))$ { proxy_pass http://docker_server/guacamole/$1; proxy_http_version 1.1; proxy_set_header Host $host; # Cache static assets for 1 hour add_header Cache-Control "public, max-age=3600"; expires 1h; } # ========================================================================= # Security - Block sensitive paths # ========================================================================= location ~ ^/(\.env|\.git|docker-compose|Dockerfile|\.htaccess|\.htpasswd) { deny all; return 404; } location ~ /\. { deny all; return 404; } # ========================================================================= # Error Pages # ========================================================================= error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location = /404.html { return 404 '{"error": "Not Found", "message": "The requested resource was not found"}'; add_header Content-Type application/json always; } location = /50x.html { return 500 '{"error": "Internal Server Error", "message": "Service temporarily unavailable"}'; add_header Content-Type application/json always; } } # ============================================================================= # WebSocket Upgrade Mapping # ============================================================================= map $http_upgrade $connection_upgrade { default upgrade; '' close; }