Ergebnis einer beauftragten Sicherheitsbegutachtung. Die WebAuthn- Zeremonie selbst ist nicht angefasst worden - sie war in Ordnung. Alle Befunde lagen DANEBEN: Ein zweiter Faktor ist nur so stark wie der schwaechste Weg zum Konto, und Sitzungen, die aus ihm entstehen, erben seinen Schutz nicht. KRITISCH /api/setup/v1/login prueste auf totp_secret - eine Spalte, die kein Anmeldeweg auswertet - und nicht auf dc_user_credentials. Ein Administratorpasswort genuegte dort fuer ein setup:install-Token ohne Hardwarekey. Neu: Konten mit zweitem Faktor abgewiesen, und die Rollentrennung gilt symmetrisch. Der Docblock behauptete sie seit jeher; erzwungen hat sie nichts. HOCH secondFactorRequired(), accountLockoutSeconds() und lockoutSeconds() fingen jedes Throwable und fielen auf "kein Faktor" bzw. "nicht gesperrt" zurueck. Gefangen wurden damit auch Verbindungsabbrueche und Lock-Timeouts - jeder davon schaltete still fuer alle Konten auf Passwort-allein. Toleriert werden jetzt nur noch 42S02 und 42S22. HOCH Fuenf Aktionen aenderten die Anmeldemittel ohne erneute Bestaetigung. Eine uebernommene Sitzung konnte sich damit dauerhaft einnisten. Neu: Authentifizierung aus den letzten zehn Minuten noetig, geprueft an derselben Stelle, an der auch der Leerlauf sperrt. WEITER - Leerlauf 30 statt 120 Minuten, und GESPERRT statt beendet: AAL2 laesst zur Reaktivierung einen Faktor genuegen. Wer alle 30 Minuten den Hardwarekey vorzeigen muss, laesst ihn stecken. - Migration 022: session_epoch. Ein Passwortwechsel beendet die uebrigen Sitzungen des Kontos, die eigene bleibt. - Kontosperre wirkt auf der zweiten Stufe; Zaehler gegen unbegrenztes Raten im Zwischenzustand. - Drosselung auf login.php (10/60s) und /api/auth/v1/ (30/60s). - Anmeldefehler ununterscheidbar; die IP-Sperre darf benannt bleiben, sie sagt nichts ueber ein Konto. - HSTS, TLS-Zwang VOR allen Routen, CSP. Die CSP traegt wegen 93 Inline-Handlern nicht gegen XSS - das steht so im Kommentar. - session.use_strict_mode, CSRF-Token bei der Anmeldung verworfen, Secure-Flag aus app.url statt aus $_SERVER['HTTPS']. - rpId/expectedOrigin nicht mehr aus dem Host-Header. /api/health meldet unter checks.webauthn_anchor, ob der Anker steht. - Notfalltoken laeuft nach 180 Tagen ab, geprueft nach dem Hashvergleich. - config.example.php ohne echtes Passwort, verify_ssl auf true. - Serverdaten.txt.bak mitgesperrt, dc_login_attempts raeumt auf. BELEGT 188 von 188 Testfaellen gruen, darunter drei neue in der Gruppe "haertung". Die Blockade einer veralteten Sitzung liess sich nicht in der Suite pruefen - sie verlangt, das Zehn-Minuten-Fenster wirklich abzuwarten. Dafuer gibt es ein Werkzeug ausserhalb; es belegt, dass der Formularweg auf den Sperrbildschirm fuehrt, die Regel dabei unveraendert bleibt, der JSON-Weg reauth_required meldet und das Passwort allein wieder oeffnet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
4.6 KiB
Python
112 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Testlauf gegen die Testinstanz.
|
|
|
|
python tests/e2e/run.py # alles, mit Zuruecksetzen
|
|
python tests/e2e/run.py --gruppe sperre # nur eine Gruppe
|
|
python tests/e2e/run.py --nur roadmap # Namensfilter
|
|
python tests/e2e/run.py --kein-reset # bestehenden Datenbestand behalten
|
|
python tests/e2e/run.py --bericht bericht.json
|
|
python tests/e2e/run.py --log # bei Fehlern das Serverlog zeigen
|
|
|
|
Rueckgabewert: 0 wenn alles bestanden ist, 1 bei Fehlschlaegen, 2 wenn der
|
|
Lauf gar nicht erst starten konnte. scripts/deploy.py wertet genau das aus und
|
|
uebertraegt nichts auf die Produktion, solange hier etwas rot ist.
|
|
|
|
Testfaelle, die einen gemeldeten und noch offenen Befund festhalten, stehen in
|
|
bekannte_befunde.json. Sie laufen mit, erscheinen als BEK - und blockieren das
|
|
Deployment nicht. Besteht so ein Fall wieder, sagt der Lauf das ausdruecklich.
|
|
|
|
Gruppen:
|
|
sperre Zugangssperre und Webroot (.htaccess)
|
|
kern Zustand, OpenAPI, Migration, allgemeines Verhalten
|
|
token Token, Rechte, Provisionierung
|
|
bugtracker Melden, Arbeit holen, uebernehmen, abschliessen
|
|
roadmap Planung
|
|
fehlerstrom Ausnahmen aus Clients
|
|
lizenz Lizenzen und Entwicklungsschluessel
|
|
watchdog Lebenszeichen, Ereignisse, Auswertung
|
|
update Releases veroeffentlichen und finden
|
|
setup Erstinstallation
|
|
supervisor Befunde
|
|
hardware Welche Authenticatoren zugelassen sind
|
|
haertung Haertung der Anmeldewege
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
import harness # noqa: E402
|
|
|
|
# Die Reihenfolge der Importe ist die Reihenfolge der Testfaelle.
|
|
import suite_sperre # noqa: E402,F401
|
|
import suite_kern # noqa: E402,F401
|
|
import suite_bugtracker # noqa: E402,F401
|
|
import suite_dienste # noqa: E402,F401
|
|
import suite_tafel # noqa: E402,F401
|
|
import suite_hardware # noqa: E402,F401
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description="Testlauf gegen die Deploymentcenter-Testinstanz")
|
|
p.add_argument('--config', help="Pfad zu deploy_dev_config.json")
|
|
p.add_argument('--gruppe', action='append', help="nur diese Gruppe (mehrfach moeglich)")
|
|
p.add_argument('--nur', help="nur Testfaelle, deren Name diesen Text enthaelt")
|
|
p.add_argument('--kein-reset', dest='reset', action='store_false',
|
|
help="Datenbank nicht zuruecksetzen (dann master_token in der Konfiguration)")
|
|
p.add_argument('--bericht', help="Ergebnisse zusaetzlich als JSON ablegen")
|
|
p.add_argument('--log', action='store_true',
|
|
help="bei Fehlschlaegen das Serverlog der Testinstanz anzeigen")
|
|
p.add_argument('--stopp', action='store_true', help="beim ersten Fehlschlag abbrechen")
|
|
p.add_argument('--liste', action='store_true', help="nur die Testfaelle auflisten")
|
|
p.add_argument('--ipv4', action='store_true',
|
|
help="IPv4 erzwingen (sonst wird IPv6 einmal geprueft und bei "
|
|
"fehlender Route automatisch umgeschaltet)")
|
|
args = p.parse_args()
|
|
|
|
cfg = harness.lade_config(args.config)
|
|
|
|
if args.liste:
|
|
gruppe = None
|
|
for fall in harness.FAELLE:
|
|
if fall.gruppe != gruppe:
|
|
gruppe = fall.gruppe
|
|
print("")
|
|
print("%s:" % gruppe)
|
|
print(" %s" % fall.name)
|
|
print("")
|
|
print("%d Testfaelle" % len(harness.FAELLE))
|
|
return 0
|
|
|
|
code = harness.run(cfg,
|
|
gruppen=args.gruppe,
|
|
muster=args.nur,
|
|
reset=args.reset,
|
|
bericht_datei=args.bericht,
|
|
stoppen=args.stopp,
|
|
adressfamilie='ipv4' if args.ipv4 else None)
|
|
|
|
if code == 1 and args.log:
|
|
# Das Log liegt hinter der Sperre; ohne diesen Weg endet ein
|
|
# fehlgeschlagener Lauf bei "HTTP 500" und dem Raten, was dahinter
|
|
# stand.
|
|
client = harness.Client(cfg['base_url'],
|
|
cfg['basic_auth']['user'], cfg['basic_auth']['pass'])
|
|
r = client.post('/testctl.php', body={'action': 'log', 'lines': 40},
|
|
token=cfg['keys']['shared_key'])
|
|
print("")
|
|
print("--- Serverlog der Testinstanz " + "-" * 40)
|
|
for datei, zeilen in (r.feld('log', default={}) or {}).items():
|
|
print("[%s]" % datei)
|
|
for zeile in zeilen:
|
|
print(" %s" % zeile)
|
|
|
|
return code
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|