diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 740ebd6..fa1ec2a 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -13,6 +13,7 @@ FROM nginx:1.27-alpine COPY nginx/default.conf /etc/nginx/conf.d/default.conf COPY nginx/security-headers.conf /etc/nginx/snippets/security-headers.conf +COPY nginx/api-proxy.conf /etc/nginx/snippets/api-proxy.conf COPY --from=frontend-build /app/dist /usr/share/nginx/html EXPOSE 80 diff --git a/nginx/api-proxy.conf b/nginx/api-proxy.conf new file mode 100644 index 0000000..ca1dc76 --- /dev/null +++ b/nginx/api-proxy.conf @@ -0,0 +1,12 @@ +# Przekazanie zadania /api/... do backendu bez zmiany adresu. Dolaczane w /api/ i w kazdym +# zagniezdzonym location z wlasnym limitem - proxy_pass nie jest dziedziczony do zagniezdzen. +proxy_pass http://backend:8080; +proxy_http_version 1.1; +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; +# Te naglowki ustawia security-headers.conf. Spring Security dodaje wlasne - zdublowane, +# sprzeczne X-Frame-Options przegladarka moze uznac za nieprawidlowe i zignorowac. +proxy_hide_header X-Frame-Options; +proxy_hide_header X-Content-Type-Options; diff --git a/nginx/default.conf b/nginx/default.conf index 0f9220f..abf465a 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -7,6 +7,25 @@ map $http_user_agent $pl_is_crawler { "~*facebookexternalhit|Facebot|Twitterbot|WhatsApp|Slackbot|LinkedInBot|TelegramBot|Discordbot|Pinterest|redditbot|Applebot|SkypeUriPreview|vkShare|Google-InspectionTool|Googlebot|bingbot|Embedly|Iframely|OpenGraph" 1; } +# Limity zadan na adres IP klienta (po realip - patrz blok server). Przekroczenie = 429. +# Limitujemy tylko ruch z naglowkiem X-Real-IP, czyli przychodzacy przez nginx hosta - na +# produkcji to caly ruch (kontener slucha tylko na 127.0.0.1 hosta, a nginx hosta zawsze ustawia +# X-Real-IP na prawdziwy adres). Lokalny ruch bez tego naglowka (dev, testy e2e) ma pusty klucz, +# a nginx nie limituje zadan z pustym kluczem. +map $http_x_real_ip $pl_limit_key { + "" ""; + default $binary_remote_addr; +} +# Logowanie i hasla: zgadywanie hasel i kodow. +limit_req_zone $pl_limit_key zone=pl_auth:10m rate=10r/m; +# Akcje wysylajace e-mail lub platny SMS: rejestracja, reset hasla, ponowne kody, formularz reklamowy. +limit_req_zone $pl_limit_key zone=pl_send:10m rate=3r/m; +# Tlumaczenia (kazde zadanie to do 150 zapytan do Google). +limit_req_zone $pl_limit_key zone=pl_translate:10m rate=120r/m; +# Cale API - ochrona przed zalewem zadan; zwykle korzystanie z serwisu jest daleko ponizej. +limit_req_zone $pl_limit_key zone=pl_api:10m rate=30r/s; +limit_req_status 429; + server { listen 80; server_name _; @@ -16,21 +35,46 @@ server { include /etc/nginx/snippets/security-headers.conf; + # Na produkcji przed kontenerem stoi nginx hosta (TLS), wiec $remote_addr to bramka Dockera - + # wszyscy uzytkownicy mieliby jeden adres i wspolny limit. Prawdziwe IP bierzemy z X-Real-IP, + # ktore ustawia nginx hosta, ale ufamy mu tylko z sieci prywatnych (port kontenera jest + # przypiety do 127.0.0.1 hosta, wiec z internetu nie da sie go podrobic). + set_real_ip_from 127.0.0.1; + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Real-IP; + + # Przekroczony limit: JSON z komunikatem, ktory frontend pokazuje uzytkownikowi (apiFetch czyta + # pole "message") zamiast ogolnego "Wystapil blad". + error_page 429 = @too_many_requests; + location @too_many_requests { + default_type application/json; + return 429 '{"status":429,"message":"Zbyt wiele prób w krótkim czasie. Odczekaj chwilę i spróbuj ponownie."}'; + } + # Wbudowany DNS Dockera - potrzebny, bo proxy_pass ze zmienna ($listing_id) rozwiazuje host # "backend" dopiero przy zadaniu, a nie przy starcie nginx. resolver 127.0.0.11 ipv6=off valid=30s; location /api/ { - proxy_pass http://backend:8080/api/; - proxy_http_version 1.1; - 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; - # Te naglowki ustawia security-headers.conf. Spring Security dodaje wlasne - zdublowane, - # sprzeczne X-Frame-Options przegladarka moze uznac za nieprawidlowe i zignorowac. - proxy_hide_header X-Frame-Options; - proxy_hide_header X-Content-Type-Options; + include /etc/nginx/snippets/api-proxy.conf; + limit_req zone=pl_api burst=200 nodelay; + + location ~ ^/api/auth/(login|activate|reset-password|verify-phone|verify-password|change-password)$ { + include /etc/nginx/snippets/api-proxy.conf; + limit_req zone=pl_auth burst=10 nodelay; + } + + location ~ ^/api/(auth/(register|forgot-password|resend-activation|resend-phone-otp)|advertising-enquiries)$ { + include /etc/nginx/snippets/api-proxy.conf; + limit_req zone=pl_send burst=5 nodelay; + } + + location = /api/i18n/translate { + include /etc/nginx/snippets/api-proxy.conf; + limit_req zone=pl_translate burst=60 nodelay; + } } # Szczegoly ogloszenia: crawler dostaje serwerowy podglad Open Graph, czlowiek - aplikacje SPA.