Files
mesh-configs/skills/devops/jit-ssh-access-portal/references/safe-e2e-login-testing.md
T

2.3 KiB

Sicheres E2E-Testen der Login-/Lockout-Logik (ohne echten User zu zerstören)

Der Bonuspitfall: Tests der Login-/Brute-Force-Logik dürfen den echten Benutzeraccount (mehlzoerwer) nicht verändern, sonst ist der echte Login danach defekt (Skill-Pitfall "E2E-Test mutiert die DB").

Muster (validiert 2026-08-04)

  1. IP-Lockout-Zweig testen → mit einem fremden/leeren Benutzernamen laufen lassen. Fremder User trifft nur den In-Memory-_IP_FAIL-Zähler, KEIN DB-Update auf den echten Account:

    for i in $(seq 1 6); do
      curl -sk -X POST https://ssh.<domain>.de/ \
        -d "username=foo&password=wrong$i"
    done
    tail portal.log   # Versuch 6 → "IP-SPERRE aktiv für <ip>"
    

    Prüfen, dass der echte User danach failed=0, lockout_until=0 ist.

  2. DB-User-Lockout-Zweig testen → temporären Test-User in die DB, nach dem Test wieder löschen:

    ./venv/bin/python -c "
    import sqlite3
    from werkzeug.security import generate_password_hash
    c=sqlite3.connect('portal.db')
    c.execute(\"INSERT OR REPLACE INTO users(username,pw_hash,pw_changed,failed,lockout_until)\
               VALUES('testuser',?,1,0,0)\", (generate_password_hash('TestPass123!'),))
    c.commit(); c.close()"
    

    Dann 5x falsches PW für testuser → DB zeigt failed=5, lockout_until=<ts>, Log zeigt "USER-SPERRE aktiv".

  3. WICHTIG — In-Memory-IP-Sperre leert nur beim Service-Restart: Nach Test 1 ist die Test-IP noch 15 min in _IP_FAIL gesperrt. Vor dem Testen des zweiten Zweigs systemctl restart sshportal (leert den In-Memory-Zähler), sonst kommen die neuen Requests gar nicht bis zur DB.

  4. Aufräumen (immer, in einer Transaktion committen):

    c.execute('DELETE FROM users WHERE username=?', ('testuser',))
    c.execute('UPDATE users SET failed=0, lockout_until=0 WHERE username=?', ('mehlzoerwer',))
    c.commit()
    

Fallstricke

  • sqlite3-CLI fehlt oft auf dem VPS → über das venv-Python lesen (./venv/bin/python -c "..."), nicht sqlite3 portal.db.
  • DB-Schema kann Alt-Spalten haben → dict(row) schlägt beim Print fehl ("length 11; 2 is required"); explizite Spalten im SELECT wählen.
  • Desktop-dict(rows)-Print ist nur Reporting, nicht Teil des Deletes — ein Traceback dort heißt NICHT, dass der Delete fehlgeschlagen ist.