# Sicherheitsarchitektur Dieses Dokument beschreibt den **aktuell implementierten** Stand nach Phase 1. ## Passwörter Gespeichert wird ausschließlich ein Argon2id-Hash im PHC-Format: ```text $argon2id$v=19$m=65536,t=3,p=4$$ ``` - **Parameter:** 64 MiB Speicher, 3 Durchgänge, Parallelität 4, 16 Byte Salz, 32 Byte Hash. Speicherbedarf ist die wirksamste Verteidigung, weil er Angriffe mit Grafikkarten und Spezialhardware unwirtschaftlich macht. - **Die Parameter wandern mit dem Hash.** Sie lassen sich später verschärfen, ohne bestehende Anmeldungen zu brechen; `NeedsRehash` erkennt veraltete Hashes. - **Vergleich in konstanter Zeit.** Eine von der Übereinstimmung abhängige Laufzeit verriete, wie viele Zeichen bereits stimmen. - **Mindestanforderung:** 12 Zeichen, mindestens 5 verschiedene, Buchstaben plus mindestens ein weiteres Zeichen. Bewusst *keine* starren Zeichenklassenregeln — die führen zu vorhersagbaren Mustern wie `Passwort1!`, ohne die Sicherheit zu erhöhen. ## Schutz vor Benutzer-Enumeration Ein unbekannter Anmeldename und ein falsches Passwort liefern **denselben** Fehler (`ErrInvalidCredentials`) und **denselben** HTTP-Status. Zusätzlich führt der Dienst bei unbekanntem Konto eine Schein-Passwortprüfung durch. Ohne sie wäre die Antwort messbar schneller als bei einem existierenden Konto — daraus liessen sich gültige Anmeldenamen ableiten. ## Zweiter Faktor TOTP nach RFC 6238, verifiziert gegen alle zehn offiziellen Testvektoren des RFC (SHA1/SHA256/SHA512). - **Eigene Umsetzung statt Fremdbibliothek:** der Algorithmus ist klein und vollständig spezifiziert, die Korrektheit über die offiziellen Vektoren nachweisbar, und Syncova braucht ohnehin eigene Logik für Replay-Schutz und Zeitfenster-Toleranz. - **Replay-Schutz:** der zuletzt akzeptierte Zeitschritt wird je Methode gespeichert. Ein Code aus diesem oder einem älteren Schritt wird abgelehnt, selbst wenn er rechnerisch stimmt. Ohne diese Prüfung liesse sich ein abgefangener Code innerhalb seines 30-Sekunden-Fensters erneut verwenden. - **Toleranz:** je ein Zeitschritt in beide Richtungen. Ohne Toleranz schlüge jede leicht falsch gehende Uhr fehl; mehr Toleranz verlängerte das Zeitfenster für einen Angreifer. - **Bestätigung erforderlich:** ein neu eingerichteter Faktor ist erst nach erfolgreicher Codeeingabe aktiv. Sonst könnte sich aussperren, wessen App das Secret nicht korrekt übernommen hat. - **Secrets liegen verschlüsselt** (AES-256-GCM) mit Angabe der Schlüsselversion. ### Wiederherstellungscodes Zehn Codes im Format `ABCDE-FGHIJ`, Alphabet ohne `0/O` und `1/I` (Verwechslungsgefahr beim Abtippen). Gespeichert wird nur der Argon2id-Hash; der Klartext erscheint genau einmal bei der Ausgabe. Jeder Code gilt genau einmal — die Entwertung ist über eine Bedingung auf `used_at` gegen gleichzeitige Anfragen abgesichert. ## Sitzungen Opake Zufallstokens (32 Byte, 256 Bit Entropie) statt JWT. Der Grund: **sofortige Widerrufbarkeit**. Ein JWT bliebe bis zum Ablauf gültig, auch nach Sperre oder Passwortänderung. - **Gespeichert wird nur der SHA-256-Hash.** Ein Datenbankleck erlaubt damit keine Übernahme laufender Sitzungen. Argon2id wäre hier ohne Nutzen: ein Token mit 256 Bit Zufall kann nicht erraten werden, die Rechenkosten brächten also nichts. - **Rotation bei jeder Erneuerung.** Beide Tokens werden ausgetauscht, ein abgefangenes Erneuerungstoken wird beim nächsten regulären Gebrauch wertlos. - **Lebensdauer:** Zugriffstoken 15 Minuten, Erneuerungstoken 12 Stunden (konfigurierbar). - **Ein gesperrtes Konto verliert den Zugriff sofort** — bei jedem Request wird der Kontozustand geprüft, eine bestehende Sitzung überdauert die Sperre nicht. - **Passwortänderung und Deaktivierung beenden alle Sitzungen** des Kontos. ## Zugriffssteuerung Sieben mitgelieferte Rollen (PROMPT.md §42) und 27 Einzelberechtigungen im Format `bereich.aktion`. | Rolle | Rechte | | --- | --- | | `super_administrator` | 27 (alle) | | `infrastructure_administrator` | 14 | | `backup_operator` | 12 | | `security_administrator` | 11 | | `auditor` | 11 (ausschließlich lesend) | | `viewer` | 10 (ausschließlich lesend) | | `restore_operator` | 10 | Durchsetzung ausschließlich serverseitig. Authentifizierung und Berechtigungsprüfung sind in `protectedHandler` **untrennbar verbunden** — ein Endpunkt lässt sich damit nicht versehentlich ohne Berechtigungsprüfung einbinden. Ein abgewiesener Zugriff erzeugt ein Auditereignis. Mitgelieferte Rollen sind unveränderlich: eine nachträgliche Änderung verschöbe die Bedeutung bestehender Zuweisungen. ### Schutz des letzten Administrators Löschung, Deaktivierung und Rollenentzug prüfen, ob danach noch ein aktives Konto mit `users.write` übrig bleibt. Andernfalls wäre die Installation nicht mehr verwaltbar. ## Brute-Force-Schutz Zwei Ebenen, die zusammenwirken: 1. **Kontobezogen:** 5 Fehlversuche → 15 Minuten Sperre. Der Zähler wird in einer einzigen SQL-Anweisung erhöht, damit gleichzeitige Versuche ihn nicht überschreiben. 2. **Absenderbezogen:** 10 Anmeldeversuche je 5 Minuten und IP-Adresse. Ohne diese Ebene könnte ein Angreifer viele Konten mit je wenigen Versuchen durchprobieren, ohne je eine Sperre auszulösen. Eine MFA-Herausforderung erlaubt 5 Fehlversuche und wird danach geschlossen — sonst liesse sich ein sechsstelliger Code durchprobieren. ## Auditprotokoll Append-only, **in der Datenbank durchgesetzt**: Trigger weisen `UPDATE` und `DELETE` auf `audit_events` ab. Die Regel liegt bewusst nicht nur in der Anwendung — ein Angreifer mit Datenbankzugriff soll seine Spuren nicht durch ein einfaches `UPDATE` verwischen können. Protokolliert werden Anmeldung (Erfolg und Fehlschlag), MFA-Ergebnisse, Kontosperren, abgewiesene Zugriffe, Benutzer- und Rollenänderungen sowie Passwortänderungen. - `actor_username` wird mitgeschrieben und bleibt erhalten, auch wenn der Benutzer später gelöscht wird — sonst verlöre das Protokoll seine Aussage. - Die Absenderadresse stammt ausschließlich aus `RemoteAddr`. Weitergeleitete Adressen aus Headern werden **nicht** ausgewertet: sie sind frei fälschbar und machten das Protokoll wertlos, solange nicht bekannt ist, welchem Proxy zu trauen ist. - Ein fehlgeschlagener Auditeintrag wird als `ERROR` geloggt und lässt die auslösende Handlung nicht scheitern — sie ist bereits geschehen, ein stiller Verlust wäre der schlechtere Ausgang. ## Secrets `SecretStore` ist eine schmale Schnittstelle, damit die lokale Umsetzung später gegen KMS, HSM oder einen externen Store getauscht werden kann. Die lokale Umsetzung nutzt AES-256-GCM (authentifiziert — jede nachträgliche Veränderung wird beim Entschlüsseln erkannt). Mehrere Schlüsselversionen können gleichzeitig vorliegen: neu verschlüsselt wird mit der aktuellen, ältere bleiben zur Entschlüsselung erhalten (Schlüsselrotation). **Der Verschlüsselungsschlüssel ist kritisch.** Ohne ihn sind alle verschlüsselten Daten dauerhaft unlesbar. Er ist getrennt von der Datenbank zu sichern. ## Secrets im Log Die Redaction hängt an `slog.HandlerOptions.ReplaceAttr` — dem einzigen Ort, an dem sie nicht vergessen werden kann. Erkannt werden Feldnamen, die auf ein Geheimnis hindeuten (`password`, `token`, `secret`, `connection_string` und weitere), unabhängig von Schreibweise und Präfix. Verbindungszeichenketten gehen nur über `RedactedConnectionString()` nach außen. Fehlermeldungen von Fremdbibliotheken, die eine DSN enthalten könnten, laufen durch `redactPasswordInText`. ## Transport Alle Antworten tragen `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, eine restriktive CSP und `Cache-Control: no-store`. CORS ist standardmäßig leer; eine Wildcard-Herkunft wird grundsätzlich abgelehnt, eine unverschlüsselte Herkunft in der Produktion ebenfalls. TLS wird in der Produktion vor dem Dienst terminiert (Reverse Proxy); der Dienst selbst bindet standardmäßig nur an `127.0.0.1`. ## Erstinbetriebnahme Es gibt **kein** vorkonfiguriertes Standardkonto — das wäre eine bekannte Schwachstelle jeder Installation. Der erste Administrator wird mit `syncova-admin create-admin` auf dem Server angelegt, das Passwort dabei verdeckt eingegeben. Das Kommando verweigert den Dienst, sobald bereits ein Administrator existiert; weitere Benutzer entstehen über die API. ## Bekannte Grenzen - **Rate-Limiting liegt im Arbeitsspeicher.** Bei mehreren Control-Plane-Knoten müsste der Zähler in eine gemeinsame Ablage wandern. - **WebAuthn/Passkeys, OIDC, SAML und LDAP** sind architektonisch vorgesehen, aber nicht implementiert. - **Four-Eyes-Freigabe** (PROMPT.md §87) ist noch nicht umgesetzt. - **Step-up-MFA für einzelne kritische Aktionen** ist noch nicht umgesetzt; MFA greift derzeit bei der Anmeldung.