setup-tls.sh: auf Server ausführbares, manuelles Skript für direktes Let's-Encrypt-Zertifikat via certbot --nginx. Bleibt ungenutzt solange der vorgeschaltete Proxy TLS+Domain übernimmt (aktueller Stand) - nginx.conf bleibt deshalb bewusst HTTP-only, certbot würde die 443-Erweiterung selbst in /etc/nginx/ einfügen, nicht ins Repo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTxkZEUdfgMxZvHPiZJ8bV
100 lines
4.8 KiB
Nginx Configuration File
100 lines
4.8 KiB
Nginx Configuration File
# HTTP-only Konfiguration – bewusst so, aktuell übernimmt ein vorgeschalteter
|
||
# Proxy TLS-Terminierung + Domain. Diese Datei bleibt deshalb HTTP-only.
|
||
#
|
||
# Optional (falls der Server künftig OHNE vorgeschalteten Proxy direkt mit
|
||
# eigenem Zertifikat laufen soll): ./setup-tls.sh <domain> auf dem Server
|
||
# ausführen (certbot --nginx, holt Zertifikat + fügt 443-Block automatisch in
|
||
# /etc/nginx/sites-available/timemaster ein, NICHT in dieses Repo-File).
|
||
|
||
server {
|
||
listen 80;
|
||
server_name _;
|
||
|
||
client_max_body_size 20M;
|
||
|
||
# API Backend
|
||
location /api/ {
|
||
proxy_pass http://127.0.0.1:8000;
|
||
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;
|
||
proxy_read_timeout 60s;
|
||
|
||
# Security Headers
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
|
||
}
|
||
|
||
# FastAPI Docs – nur aus internem LAN erreichbar (API-Struktur sonst für
|
||
# Angreifer sichtbar). LAN-Bereich ggf. an tatsächliches Subnetz anpassen.
|
||
location /docs {
|
||
allow 192.168.1.0/24;
|
||
allow 127.0.0.1;
|
||
deny all;
|
||
|
||
proxy_pass http://127.0.0.1:8000/docs;
|
||
|
||
# Security Headers
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
|
||
}
|
||
|
||
location /openapi.json {
|
||
allow 192.168.1.0/24;
|
||
allow 127.0.0.1;
|
||
deny all;
|
||
|
||
proxy_pass http://127.0.0.1:8000/openapi.json;
|
||
|
||
# Security Headers
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
|
||
}
|
||
|
||
# React Frontend (statische Dateien)
|
||
# HINWEIS: nginx-Regel: add_header in einem location-Block ueberschreibt
|
||
# alle add_header-Direktiven des parent server-Blocks. Daher Security-Header
|
||
# in jede location wiederholen.
|
||
location / {
|
||
root /opt/timemaster/frontend/dist;
|
||
index index.html;
|
||
try_files $uri $uri/ /index.html;
|
||
expires 1d;
|
||
|
||
add_header Cache-Control "public, must-revalidate";
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
|
||
}
|
||
|
||
# Uploads / Static Files
|
||
location /static/ {
|
||
alias /opt/timemaster/backend/static/;
|
||
expires 7d;
|
||
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
|
||
}
|
||
}
|