diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index bc533c6..4136766 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -35,9 +35,10 @@ services: APP_ADMIN_NAME: ${APP_ADMIN_NAME:-Administrator Polska Lokalnie} # true = przy starcie ustaw haslo administratora na APP_ADMIN_PASSWORD (odzyskanie dostepu). APP_ADMIN_RESET_PASSWORD: ${APP_ADMIN_RESET_PASSWORD:-false} - # Sekret podpisujacy tokeny sesji. Domyslna wartosc jest jawna w repozytorium - w produkcji - # musi byc wlasna, inaczej kazdy moze wystawic sobie token administratora. - APP_JWT_SECRET: ${APP_JWT_SECRET:-local-dev-secret-change-me-please-32chars-min} + # Sekret podpisujacy tokeny sesji - bez wartosci domyslnej. Gdyby .env.production sie nie + # wczytal, wdrozenie ma sie zatrzymac, a nie wystartowac z jawnym sekretem z repozytorium + # (z nim kazdy moglby wystawic sobie token administratora). + APP_JWT_SECRET: ${APP_JWT_SECRET:?Brak APP_JWT_SECRET w .env.production} APP_PUBLIC_BASE_URL: ${APP_PUBLIC_BASE_URL:-http://localhost} depends_on: db: diff --git a/nginx/Dockerfile b/nginx/Dockerfile index f22c495..740ebd6 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,8 +1,10 @@ FROM node:22-alpine AS frontend-build WORKDIR /app -COPY frontend/package.json ./ -RUN npm install +# Instalacja dokladnie wersji z package-lock.json - bez lockfile'a kazde wdrozenie moglo dociagnac +# inne (w tym podmienione) wersje zaleznosci. +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci COPY frontend/ ./ RUN npm run build @@ -10,6 +12,7 @@ RUN npm run build 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 --from=frontend-build /app/dist /usr/share/nginx/html EXPOSE 80 diff --git a/nginx/default.conf b/nginx/default.conf index 8889190..0f9220f 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -14,6 +14,8 @@ server { root /usr/share/nginx/html; index index.html; + include /etc/nginx/snippets/security-headers.conf; + # 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; @@ -25,6 +27,10 @@ server { 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; } # Szczegoly ogloszenia: crawler dostaje serwerowy podglad Open Graph, czlowiek - aplikacje SPA. @@ -32,6 +38,8 @@ server { if ($pl_is_crawler) { proxy_pass http://backend:8080/api/og/listing/$listing_id; } + proxy_hide_header X-Frame-Options; + proxy_hide_header X-Content-Type-Options; try_files $uri /index.html; } @@ -39,12 +47,14 @@ server { location /assets/ { try_files $uri =404; add_header Cache-Control "public, max-age=31536000, immutable"; + include /etc/nginx/snippets/security-headers.conf; } # index.html nigdy nie moze byc cache'owany - inaczej przegladarka po wdrozeniu # nadal laduje stary bundle i uzytkownik pracuje na nieaktualnej wersji aplikacji. location = /index.html { add_header Cache-Control "no-store, must-revalidate"; + include /etc/nginx/snippets/security-headers.conf; } location / { diff --git a/nginx/security-headers.conf b/nginx/security-headers.conf new file mode 100644 index 0000000..5a5bfd6 --- /dev/null +++ b/nginx/security-headers.conf @@ -0,0 +1,16 @@ +# Naglowki bezpieczenstwa dla wszystkich odpowiedzi. Dolaczane w bloku server ORAZ w kazdym +# location z wlasnym add_header - nginx nie dziedziczy add_header z server do takiego location. + +# Przegladarka nie zgaduje typu pliku (obrazek nie zostanie wykonany jako HTML/skrypt). +add_header X-Content-Type-Options "nosniff" always; +# Serwis nie moze byc osadzony w ramce na obcej stronie (clickjacking). +add_header X-Frame-Options "SAMEORIGIN" always; +# Adres podstrony (np. z identyfikatorem rozmowy) nie wycieka do obcych serwisow. +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +# Tylko HTTPS przez rok. TLS terminuje nginx hosta; przy http://localhost przegladarka ignoruje naglowek. +add_header Strict-Transport-Security "max-age=31536000" always; +# Bez dostepu do kamery/mikrofonu/platnosci przegladarki; geolokalizacja tylko dla samego serwisu. +add_header Permissions-Policy "camera=(), microphone=(), payment=(), geolocation=(self)" always; +# Minimalna polityka CSP, ktora nie ogranicza skryptow ani zasobow aplikacji: blokuje wtyczki, +# podmiane i osadzanie w obcych ramkach. +add_header Content-Security-Policy "object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;