security: 9 Findings aus Security-Audit behoben (CRITICAL + HIGH + MEDIUM)

CRITICAL:
- C-1: LDAP tls_verify Default False → True (MITM-Schutz)
- C-2: TOTP-Secret Fernet-verschlüsselt in DB (statt Plaintext)
  - core/crypto.py: encrypt_value() / decrypt_value() helper
  - Migration 0026: totp_secret VARCHAR(64→500), ldap tls_verify default=true
  - _totp_plain() helper mit Legacy-Fallback für bestehende Werte

HIGH:
- H-1: Kiosk Nonce-Cache asyncio.Lock (Race Condition behoben)
- H-2: File-Upload-Limit 10 MB (import_kimai.py + users.py CSV-Import)
- H-3: CORS allow_methods/allow_headers explizit eingeschränkt (war *)
- H-4: TrustedHostMiddleware aktiviert wenn ALLOWED_HOSTS gesetzt

MEDIUM:
- M-1: IP-Logging nutzt X-Forwarded-For hinter nginx-Proxy
- M-4: Audit-Log für password_changed, totp_enabled, totp_disabled
- M-5: CalDAV verify_ssl in Production erzwungen (_effective_verify_ssl)

152/152 Tests grün

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 19:45:09 +02:00
parent a639de13f8
commit 62c4e742ab
12 changed files with 319 additions and 31 deletions
+42
View File
@@ -0,0 +1,42 @@
"""
Zentrale Krypto-Hilfsfunktionen für TimeMaster.
Verwendet Fernet-Verschlüsselung (AES-128-CBC + HMAC-SHA256).
Der Schlüssel wird aus SECRET_KEY per SHA-256 abgeleitet.
Verwendung:
from app.core.crypto import encrypt_value, decrypt_value
stored = encrypt_value("geheimes-passwort")
plain = decrypt_value(stored)
"""
from __future__ import annotations
import base64
import hashlib
from cryptography.fernet import Fernet, InvalidToken
from app.core.config import settings
def _fernet() -> Fernet:
"""Erstellt eine Fernet-Instanz aus dem konfigurierten SECRET_KEY."""
key = hashlib.sha256(settings.secret_key.encode()).digest()
return Fernet(base64.urlsafe_b64encode(key))
def encrypt_value(plain: str) -> str:
"""Verschlüsselt einen Klartext-String per Fernet. Gibt den chiffrierten String zurück."""
return _fernet().encrypt(plain.encode()).decode()
def decrypt_value(encrypted: str) -> str:
"""
Entschlüsselt einen Fernet-verschlüsselten String.
Wirft ValueError bei ungültigem Token oder falschem Schlüssel.
"""
try:
return _fernet().decrypt(encrypted.encode()).decode()
except InvalidToken as exc:
raise ValueError("Entschlüsselung fehlgeschlagen ungültiger Token oder falscher Schlüssel.") from exc