From d853101275628a7cb0cb0bb44c0047958724515e Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Wed, 6 May 2026 23:31:51 +0200 Subject: [PATCH] feat(Phase 11b): Bypass-DB-Logging + Auto-Rotation-Skript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auth_bypass_uses-Tabelle additiv (used_at, client_ip, path, user_agent). - _check_debug_token schreibt jeden Use als Best-Effort-Insert (Try/Except, kein Fehler an User). - scripts/rotate-debug-token.sh: wöchentlicher Cron, generiert neues Secret + re-creates dev-Container. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/auth.py | 21 +++++++++++++++++++- app/database.py | 16 ++++++++++++++++ scripts/rotate-debug-token.sh | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100755 scripts/rotate-debug-token.sh 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"