Befund aus dem Betrieb: Ein Passkey aus 1Password liess sich als Hardwarekey registrieren. Fuer das Deploymentcenter hinnehmbar, fuer ein Protokoll, das ausdruecklich Hardware fordert, nicht. Zwei Dinge, die wie eine Pruefung aussahen und keine waren - beide seit 2.9.0 gesetzt: authenticatorAttachment "cross-platform" steuert, was der Browser anbietet, und ein Passwortverwalter meldet sich ebenso. Und response.authenticatorAttachment setzt der Client, also genau die Instanz, gegen die sich nichts beweisen laesst. Was traegt, ist Basic Attestation. Neu geprueft wird die Kette bis zu einer hinterlegten Herstellerwurzel; die drei FIDO-Wurzeln von Yubico liegen bei. Ein x5c allein genuegt ausdruecklich nicht: Der Fall "eigenes Zertifikat, Name Yubico, AAGUID eines echten YubiKey 5, gueltige Unterschrift" wurde nachgebaut und wird abgewiesen - an der Kette, nicht an der Signatur. Drei Stufen statt eines Schalters, weil alle bisher registrierten Schluessel als nicht nachgewiesen gelten - auch echte Yubikeys. Bei attestation "none" ersetzt der Browser die Geraetekennung durch Nullbytes, bevor der Server sie sieht; es gab also nie eine Herkunft. Ein einzelner Schalter haette beim Umlegen den eigenen Zugang gesperrt. Der Wechsel nach "strict" wird zusaetzlich abgelehnt, solange das eigene Konto keinen nachgewiesenen Schluessel hat. - Attestation.php: packed und fido-u2f, Kettenpruefung gegen eigene Wurzeln statt gegen den Speicher des Betriebssystems, Gueltigkeit, Gegenprobe der AAGUID aus der Zertifikatserweiterung - HardwarePolicy.php: off / register / strict, optionale Modellliste, Wurzeln aus der Konfiguration erweiterbar - Migration 021: attested und drei Anzeigespalten, Vorgabe 0 - WebUI: Herkunft je Schluessel, Regel vor der Beruehrung angesagt - testctl clear_credentials, damit ein Testlauf den naechsten nicht aussperrt - Testreihe "hardware", 5 Faelle, ohne Krypto-Bibliothek 184 von 184 Testfaellen gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
111 lines
4.5 KiB
Python
111 lines
4.5 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
|
|
"""
|
|
|
|
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())
|