- security.py/dependencies.py/auth.py: jose -> pyjwt (unmaintained, Algorithm-Confusion-CVE). API-kompatibel (jwt.encode/decode gleich). - requirements.txt: alle Versionen gepinnt (waren >=, jetzt == anhand aktueller 137-Installation) fuer reproduzierbare Deploys. - nginx.conf: /docs + /openapi.json nur noch aus LAN erreichbar (waren oeffentlich, API-Struktur-Leak). fail2ban auf 137+164 installiert (sshd + nginx-badbots + timemaster-auth Jails), Configs nicht im Repo (Server-only, /etc/fail2ban/). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTxkZEUdfgMxZvHPiZJ8bV
99 lines
4.7 KiB
Nginx Configuration File
99 lines
4.7 KiB
Nginx Configuration File
# HTTP-only Konfiguration (SSL/HTTPS noch nicht eingerichtet)
|
||
# Sobald ein TLS-Zertifikat vorhanden ist:
|
||
# 1. Listen-Block auf 443 ssl http2 erweitern
|
||
# 2. ssl_certificate / ssl_certificate_key einkommentieren
|
||
# 3. HSTS-Header hinzufügen
|
||
# 4. HTTP->HTTPS-Redirect aktivieren
|
||
|
||
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;
|
||
}
|
||
}
|