diff --git a/app/auth.py b/app/auth.py index e4bc754..9ca1f36 100644 --- a/app/auth.py +++ b/app/auth.py @@ -116,10 +116,29 @@ def _check_debug_token(request: Request) -> Optional[dict]: presented = (request.query_params.get("__debug_token") or "").strip() if presented and presented == expected: client = request.client.host if request.client else "?" + path = request.url.path + user_agent = request.headers.get("user-agent", "")[:200] logger.warning( "DEBUG_AUTH_TOKEN bypass active — request from %s to %s", - client, request.url.path, + client, path, ) + # Best-Effort-Logging in DB (synchron, fire-and-forget). + # Schlägt unter keinen Umständen den Request fehl. + try: + import sqlite3 + from .config import settings + conn = sqlite3.connect(str(settings.db_path), timeout=2) + try: + conn.execute( + "INSERT INTO auth_bypass_uses (client_ip, path, user_agent) " + "VALUES (?, ?, ?)", + (client, path, user_agent), + ) + conn.commit() + finally: + conn.close() + except Exception: + pass # Logging-Fehler nie an User propagieren return { "sub": "debug-user", "email": "debug@local", diff --git a/app/database.py b/app/database.py index 5d65f68..dc56d47 100644 --- a/app/database.py +++ b/app/database.py @@ -363,6 +363,22 @@ async def init_db(): "ON auto_rate_runs(started_at DESC)" ) + # auth_bypass_uses (Phase 11b) — Tracking jeder Verwendung des + # DEBUG_AUTH_TOKEN-Bypass (siehe app/auth.py:_check_debug_token). + await db.execute(""" + CREATE TABLE IF NOT EXISTS auth_bypass_uses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + used_at TEXT NOT NULL DEFAULT (datetime('now')), + client_ip TEXT, + path TEXT NOT NULL, + user_agent TEXT + ) + """) + await db.execute( + "CREATE INDEX IF NOT EXISTS idx_auth_bypass_used " + "ON auth_bypass_uses(used_at DESC)" + ) + await db.commit() diff --git a/scripts/rotate-debug-token.sh b/scripts/rotate-debug-token.sh new file mode 100755 index 0000000..f159843 --- /dev/null +++ b/scripts/rotate-debug-token.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Auto-Rotation des DEBUG_AUTH_TOKEN-Bypass-Secrets (Phase 11b). +# +# Generiert wöchentlich ein neues Secret, schreibt es in +# /opt/gwoe-antragspruefer-dev/.env und re-creates den Container, damit +# die ENV-Änderung greift. +# +# Cron (Sonntag 04:00): +# 0 4 * * 0 /opt/gwoe-antragspruefer-dev/scripts/rotate-debug-token.sh \ +# >> /var/log/gwoe-rotate-debug.log 2>&1 + +set -euo pipefail + +ENV_FILE="/opt/gwoe-antragspruefer-dev/.env" +COMPOSE_FILE="/opt/gwoe-antragspruefer-dev/docker-compose.dev.yml" + +if [ ! -f "$ENV_FILE" ]; then + echo "$(date -Iseconds) FAIL — $ENV_FILE not found" + exit 1 +fi + +NEW_TOKEN=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))") + +# Bestehenden Eintrag entfernen, neuen anhängen — atomar via temp-Datei +TMP=$(mktemp) +grep -v "^DEBUG_AUTH_TOKEN=" "$ENV_FILE" > "$TMP" +echo "DEBUG_AUTH_TOKEN=$NEW_TOKEN" >> "$TMP" +mv "$TMP" "$ENV_FILE" + +echo "$(date -Iseconds) ROTATED — new token written to $ENV_FILE" + +# Container re-creates (compose liest .env nur beim Container-Create). +cd /opt/gwoe-antragspruefer-dev +docker compose -f "$COMPOSE_FILE" up -d --force-recreate + +echo "$(date -Iseconds) END"