Konsolidierte Funde aus postgres-/sql-/jwt-/owasp-top10-expert-Review: - Race Conditions gefixt: doppelte aktive Kontrolle (SAVEPOINT + partieller Unique-Index), doppelte Mindermengen-Genehmigung (FOR UPDATE + Unique-Index), Lost-Update bei Nachfüllung (FOR UPDATE auf Fehlbestand/Objektposition). - Migration 0007: partielle Unique-Indizes als DB-Sicherheitsnetz + fehlende FK-Indizes (fehlbestand.material_id, kontrolle(objekt_id,status), zustaendigkeit, benutzer_rolle.rolle, objektposition.ablaufdatum u.a.). - Connection-Pool explizit begrenzt (pool_size=5, max_overflow=5) - ohne das könnte jeder uvicorn-Worker den Postgres max_connections-Wert sprengen. - Timing-Angriff bei Login-Enumeration gefixt (konstante Antwortzeit über Dummy-Hash), JWT-Decode verlangt jetzt exp/sub-Claims. - App-seitiges Rate-Limiting (slowapi, 5/min) auf /auth/login als Verteidigung in der Tiefe zusätzlich zum nginx-Limit. - nginx: TLS mit selbstsigniertem Zertifikat (LAN-Betrieb, keine Domain), HSTS, Content-Security-Policy, Permissions-Policy ergänzt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
113 lines
4.8 KiB
Bash
Executable File
113 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# MABEA – Server-Setup (Debian 13). Installiert PostgreSQL + nginx, legt DB/Rolle an,
|
||
# richtet nginx als Reverse-Proxy vor dem späteren FastAPI/Uvicorn-Backend ein.
|
||
# Idempotent: mehrfacher Aufruf überschreibt keine bestehende DB-Rolle/DB.
|
||
#
|
||
# Ausführung auf dem Zielserver als root:
|
||
# DB_PASSWORD=... APP_DOMAIN=mabea.example.org ./install_server.sh
|
||
# Ohne DB_PASSWORD wird ein zufälliges Passwort erzeugt und am Ende ausgegeben
|
||
# (sofort sichern, z. B. in backend/.env auf dem Server – nicht committen).
|
||
|
||
set -euo pipefail
|
||
|
||
DB_NAME="${DB_NAME:-mabea}"
|
||
DB_USER="${DB_USER:-mabea}"
|
||
DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -base64 24)}"
|
||
JWT_SECRET_KEY="${JWT_SECRET_KEY:-$(openssl rand -base64 48)}"
|
||
APP_DOMAIN="${APP_DOMAIN:-_}" # "_" = nginx catch-all, solange keine Domain feststeht
|
||
|
||
# Kein sudo-Dependency: minimale LXC-Images haben sudo oft nicht vorinstalliert,
|
||
# Skript läuft ohnehin als root (Ausführung dokumentiert das oben).
|
||
# SQL wird bewusst über stdin an psql gepiped statt als su-Argument übergeben –
|
||
# vermeidet doppelte Shell-Quoting-/Word-Splitting-Fallstricke (Bugfix nach Testlauf).
|
||
run_psql() {
|
||
local db="$1" sql="$2"
|
||
printf '%s\n' "${sql}" | su -s /bin/bash postgres -c "psql -tA -d '${db}'"
|
||
}
|
||
|
||
echo "== apt update/install =="
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -qq
|
||
apt-get install -y -qq postgresql nginx python3-venv python3-pip git openssl
|
||
|
||
echo "== PostgreSQL: Rolle + Datenbank =="
|
||
if [ "$(run_psql postgres "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}';")" != "1" ]; then
|
||
run_psql postgres "CREATE ROLE ${DB_USER} WITH LOGIN PASSWORD '${DB_PASSWORD}';"
|
||
echo "Rolle ${DB_USER} angelegt."
|
||
else
|
||
echo "Rolle ${DB_USER} existiert bereits, überspringe."
|
||
fi
|
||
|
||
if [ "$(run_psql postgres "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}';")" != "1" ]; then
|
||
run_psql postgres "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};"
|
||
echo "Datenbank ${DB_NAME} angelegt."
|
||
else
|
||
echo "Datenbank ${DB_NAME} existiert bereits, überspringe."
|
||
fi
|
||
|
||
# gen_random_uuid() ist seit PG13 built-in, aber pgcrypto schadet nicht und macht es
|
||
# explizit/robust gegen ältere Backports (Review-Fund postgres-expert).
|
||
run_psql "${DB_NAME}" "CREATE EXTENSION IF NOT EXISTS pgcrypto;"
|
||
|
||
echo "== PostgreSQL: Speicher-Tuning für kleinen VPS (4GB RAM) =="
|
||
PG_CONF_DIR=$(run_psql postgres "SHOW config_file;" | xargs dirname)
|
||
mkdir -p "${PG_CONF_DIR}/conf.d"
|
||
cat > "${PG_CONF_DIR}/conf.d/mabea-tuning.conf" <<'PGCONF'
|
||
# MABEA: konservatives Tuning für ~4GB RAM VPS, nicht enterprise-scale.
|
||
shared_buffers = 1GB
|
||
effective_cache_size = 3GB
|
||
maintenance_work_mem = 256MB
|
||
work_mem = 8MB
|
||
max_connections = 50
|
||
PGCONF
|
||
grep -q "conf.d/\*.conf" "${PG_CONF_DIR}/postgresql.conf" || \
|
||
echo "include_dir = 'conf.d'" >> "${PG_CONF_DIR}/postgresql.conf"
|
||
|
||
echo "== TLS: selbstsigniertes Zertifikat (LAN-Betrieb ohne Domain, Karte owasp-Review) =="
|
||
mkdir -p /etc/nginx/ssl
|
||
if [ ! -f /etc/nginx/ssl/mabea.crt ]; then
|
||
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
||
-keyout /etc/nginx/ssl/mabea.key -out /etc/nginx/ssl/mabea.crt \
|
||
-subj "/CN=${APP_DOMAIN}"
|
||
chmod 600 /etc/nginx/ssl/mabea.key
|
||
echo "Selbstsigniertes Zertifikat erzeugt (10 Jahre gültig) - Browser warnen beim ersten Zugriff, bis manuell akzeptiert/importiert."
|
||
else
|
||
echo "Zertifikat existiert bereits, überspringe."
|
||
fi
|
||
|
||
echo "== nginx Reverse-Proxy Config (inkl. Security-Header, TLS) =="
|
||
sed -e "s/__APP_DOMAIN__/${APP_DOMAIN}/g" \
|
||
"$(dirname "$0")/nginx_mabea.conf.template" > /etc/nginx/sites-available/mabea.conf
|
||
ln -sf /etc/nginx/sites-available/mabea.conf /etc/nginx/sites-enabled/mabea.conf
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
nginx -t
|
||
|
||
echo "== Dienste aktivieren =="
|
||
systemctl enable --now postgresql
|
||
systemctl restart postgresql # Tuning-Konfiguration laden
|
||
systemctl enable --now nginx
|
||
systemctl reload nginx
|
||
|
||
echo "== Zugangsdaten sichern =="
|
||
CRED_FILE="/root/mabea_db_credentials.env"
|
||
umask 077
|
||
cat > "${CRED_FILE}" <<EOF
|
||
DB_NAME=${DB_NAME}
|
||
DB_USER=${DB_USER}
|
||
DB_PASSWORD=${DB_PASSWORD}
|
||
DATABASE_URL=postgresql+asyncpg://${DB_USER}:${DB_PASSWORD}@localhost:5432/${DB_NAME}
|
||
JWT_SECRET_KEY=${JWT_SECRET_KEY}
|
||
EOF
|
||
chmod 600 "${CRED_FILE}"
|
||
|
||
echo
|
||
echo "=== Fertig ==="
|
||
echo "Zugangsdaten liegen (chmod 600) in ${CRED_FILE} – NICHT auf stdout ausgegeben (Review-Fund: keine Secrets im Terminal-Scrollback)."
|
||
echo "DATABASE_URL von dort in backend/.env auf dem Server übernehmen (siehe backend/example.env)."
|
||
echo "Backend selbst ist noch nicht deployt – siehe deploy/mabea-backend.service.template für den späteren systemd-Dienst."
|
||
echo
|
||
echo "nginx läuft über HTTPS mit selbstsigniertem Zertifikat (Port 80 leitet auf 443 um)."
|
||
echo "Browser warnen beim ersten Zugriff - Zertifikat manuell akzeptieren/importieren."
|
||
echo "Sobald eine echte Domain feststeht: ssl_certificate/-key in /etc/nginx/sites-available/mabea.conf"
|
||
echo "auf ein Let's-Encrypt-Zertifikat umstellen (certbot)."
|