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
@@ -0,0 +1,68 @@
"""Security-Fixes: LDAP tls_verify default=True, totp_secret column length 500
Revision ID: 0026
Revises: 0025
Create Date: 2026-05-24
Änderungen:
- ldap_configs.tls_verify: DEFAULT False → DEFAULT True
Neue Konfigurationen werden mit TLS-Verifikation angelegt.
BESTEHENDE Einträge mit tls_verify=False werden NICHT automatisch geändert,
da dies laufende LDAP-Verbindungen unterbrechen könnte.
Admins müssen ihre LDAP-Konfiguration manuell prüfen.
- users.totp_secret: VARCHAR(64) → VARCHAR(500)
Fernet-verschlüsselte Secrets sind länger als 64 Zeichen (~180 Zeichen).
Bestehende Plaintext-Secrets (falls vorhanden) müssen manuell verschlüsselt
werden; nach dieser Migration werden neue Secrets automatisch verschlüsselt.
WICHTIG: Nach dem Deployment müssen bestehende TOTP-Nutzer ihr 2FA neu einrichten
(altes Plaintext-Secret nicht mehr kompatibel mit neuem Decrypt-Flow).
Alternativ: vor dem Deployment `migrate_totp_secrets.py` ausführen (TODO).
"""
from alembic import op
import sqlalchemy as sa
revision = "0026"
down_revision = "0025"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ── C-1: LDAP tls_verify Default auf True ────────────────────────────────
op.alter_column(
"ldap_configs",
"tls_verify",
server_default=sa.text("true"),
existing_type=sa.Boolean(),
existing_nullable=False,
)
# ── C-2: totp_secret Column-Länge für Fernet-Token ───────────────────────
op.alter_column(
"users",
"totp_secret",
existing_type=sa.String(64),
type_=sa.String(500),
existing_nullable=True,
)
def downgrade() -> None:
# totp_secret zurück auf 64 (ggf. Datenverlust bei Fernet-verschlüsselten Werten)
op.alter_column(
"users",
"totp_secret",
existing_type=sa.String(500),
type_=sa.String(64),
existing_nullable=True,
)
# LDAP tls_verify Default zurück auf False
op.alter_column(
"ldap_configs",
"tls_verify",
server_default=sa.text("false"),
existing_type=sa.Boolean(),
existing_nullable=False,
)