Connector-Channel & Drift-Schutz
Status: LIVE seit 2026-05-20
Dieses Dokument beschreibt, wie der prilog-matrix-connector (das Python-Modul, das Synapse den JWT-Login + Room-Policy-Hooks beibringt) produktiv ausgerollt wird, und welche Schutzschichten dafür sorgen, dass eine kaputte Connector-Version den Kunden-Login nicht abschalten kann.
Es richtet sich an alle, die am Backend, an Tenant-Boxen oder am Reconcile-System arbeiten — und an Neue, die im Krisenfall die Heilungsroutine durchlaufen müssen.
Hintergrund: warum diese Architektur überhaupt existiert
Synapse spricht für jeden Login-Request seine Module-API an. Wir haben dort den prilog-matrix-connector eingehängt, der zwei Dinge tut:
- JWT-Login-Provider — akzeptiert HS256-JWTs, die das Backend mintet (siehe Synapse JWT-only Login). Ohne diesen Hook akzeptiert Synapse nur klassische Passwörter (oder im JWT-only-Modus gar keinen Login).
- on_create_room-Hook — leitet jede Raum-Erstellung an das Prilog-Backend zur Policy-Prüfung weiter.
Der Connector wird als Bind-Mount in den Synapse-Container gehängt:
host: /srv/tenants/<slug>/connectors/prilog-matrix-connector/src
↓ (bind-mount)
container: /modules/prilog-matrix-connector/src
↓ (PYTHONPATH)
synapse: import prilog_matrix_connectorBeim Reconcile (oder beim Provisioning eines neuen Tenants) wird der Connector aus einem Tarball auf api.prilog.chat per scp + tar -xzf auf die Tenant-Box geschrieben. Genau in dieser Pipeline liegt das Risiko, gegen das die folgenden Schichten schützen.
Was 2026-05-20 schiefging — als konkretes Beispiel
Am 20. Mai 2026 fielen alle Logins auf drei Shared-Tenants aus mit der Meldung „Matrix-Token ungültig oder abgelaufen". Die Ursache war eine Kette:
- Das „Latest"-Tarball für den Connector (
prilog-matrix-connector-latest.tar.gz) war auf eine alte Versionf0480eazurückgefallen. Diese Version registriert nur denon_create_room-Hook — keinen JWT-Login-Provider. - Der Reconcile-Cron um 03:30 UTC sah einen Hash-Drift („Container hat Version X, Latest ist Y") und installierte die ältere Version auf allen Tenants.
- Damit war der JWT-Login-Pfad weg. Das Backend bekam
M_FORBIDDEN: Invalid username or passwordzurück und übersetzte das inINVALID_MATRIX_TOKEN.
Sechs Schutzschichten — alle additiv, jede einzelne hätte den Vorfall verhindert — sorgen heute dafür, dass das nicht mehr passieren kann.
Die sechs Schutzschichten
1. CI-Build mit Sanity-Check
Repo: brasilspace/prilog-matrix-connector, Workflow .github/workflows/build.yml.
Jeder Push auf main:
- prüft, ob
src/prilog_matrix_connector/module.pyden Stringregister_password_auth_provider_callbacksenthält. Fehlt er, bricht der Build mit GitHub-Action-Error → kein Tarball wird hochgeladen. - packt einen Tarball
prilog-matrix-connector.tar.gzund schreibt ihn in dasprilog-artifacts-Repo als zwei Dateien:prilog-matrix-connector-<sha>.tar.gz(immutable, versioniert)prilog-matrix-connector-latest.tar.gz(überschrieben pro Push)
Wichtig: CI fasst die Datei prilog-matrix-connector-stable.tar.gz nie an. Nur ein Mensch via Promote-Skript darf sie setzen.
2. Promote-Skript mit Sanity-Check
Datei: /var/www/backend-api/scripts/promote-connector.sh auf api.prilog.chat.
So sieht der Promote-Vorgang aus:
# Verfügbare Versionen anzeigen
bash /var/www/backend-api/scripts/promote-connector.sh --list
# Aktuelle Production-Version anzeigen
bash /var/www/backend-api/scripts/promote-connector.sh --current
# Eine konkrete Version als stable freigeben
bash /var/www/backend-api/scripts/promote-connector.sh <sha>
# Den aktuellen latest-Stand als stable freigeben (selten — meist will man explizit)
bash /var/www/backend-api/scripts/promote-connector.sh latestVor dem Symlink-Setzen extrahiert das Skript das Ziel-Tarball nach /tmp und prüft erneut, ob module.py den JWT-Hook enthält. Fehlt er, bricht es mit Exit-Code 2 und der Symlink bleibt unverändert. Auch ein Mensch kann also keine Login-tote Version produktiv setzen.
Nach erfolgreichem Promote:
pm2 restart backend-api # leert den 60s-Cache des Drift-Detectors
cd /var/www/backend-api && npx tsx scripts/reconcile-tenant.ts --all(Ohne Eingriff zieht der nächste Reconcile-Cron um 03:30 UTC automatisch nach.)
3. stable- vs latest-Channel
Backend-Code: src/services/tenant-reconcile/connector-artifact.ts (Drift-Erkennung) und fix-operations.ts installMatrixConnector() (Installation).
Beide Pfade lesen jetzt:
1. /var/www/prilog-artifacts/matrix-connector/prilog-matrix-connector-stable.tar.gz
↓ (Fallback nur, wenn stable noch nie gesetzt wurde)
2. /var/www/prilog-artifacts/matrix-connector/prilog-matrix-connector-latest.tar.gzSobald promote-connector.sh einmal lief, ist stable gesetzt und latest wird vom Backend ignoriert. CI darf latest weiter rotieren (für Tests oder als Quelle für den nächsten Promote), ohne dass irgendein Tenant davon angefasst wird.
4. Drift-Detector-Sanity-Check
Datei: src/services/tenant-reconcile/connector-artifact.ts, Funktion getExpectedConnectorModuleSha256().
Vor jedem Drift-Vergleich liest das Service das Soll-Tarball ein und prüft im Inhalt der module.py, ob der JWT-Auth-Provider-Hook enthalten ist. Fehlt er, gibt die Funktion null zurück — der Drift-Detector überspringt dann den Connector-Check vollständig statt einen Rollback auf eine kaputte Version anzustoßen.
Zusätzlich wird sofort eine Mail an die Ops-Adresse geschickt (siehe Schicht 6):
[Prilog Ops] Connector-Artifact fehlt der JWT-Auth-Provider-HookHeißt: Selbst wenn jemand stable direkt überschreiben würde (was das Promote-Skript verhindert, aber theoretisch geht), sieht das Backend „Kein gültiges Sollbild verfügbar" → kein automatischer Schaden.
5. Reconcile-Smoke mit Auto-Rollback
Datei: src/services/tenant-reconcile/fix-operations.ts, Funktion installMatrixConnector().
Nach jedem tar -xzf läuft automatisch:
docker compose -f /srv/tenants/<slug>/docker-compose.yml restart synapse— Bind-Mount-Inode wird neu aufgelöst (sonst zeigt der Container auf das alte, jetzt verwaiste Verzeichnis).jwtLoginSmoke(tenantId)— mintet ein Test-JWT, sendetm.login.passwordan den Synapse-Container, erwartet HTTP 200 +access_token.- Bei Smoke-Fehler: Rollback. Die alte Version (
/srv/tenants/<slug>/connectors/prilog-matrix-connector.old) wird wieder aktiviert, Synapse erneut restartet, eine Mail an Ops geht raus. Der Reconcile meldetFixes: 0 applied, 1 failed. - Bei Smoke-Erfolg:
.old-Verzeichnis wird aufgeräumt, Reconcile meldetFixes: 1 applied, JWT-Login-Smoke OK.
Das ist der Sicherheitsgurt. Selbst wenn vier Schichten versagen würden und das Backend wirklich versucht, eine kaputte Version zu installieren — diese Schicht stellt sicher, dass der Tenant nach dem Reconcile mindestens so funktionsfähig ist wie vorher.
6. JWT-Login-Watch alle 30 Minuten
Datei: src/services/login-watch.service.ts, Cron-Eintrag in src/core/cron/jobs.ts als jwt-login-watch.
Cron-Schedule: */30 * * * * (alle 30 Minuten).
Pro Tenant (mit gesetztem synapse_jwt_secret — dedicated Hosts ohne JWT-Pfad werden übersprungen):
synapseLoginViaJwt(tenantId, adminUsername)— gleiche Funktion wie im echten Login-Endpunkt.- Sofort
/_matrix/client/v3/logoutmit dem frisch erhaltenen Token. Das ist wichtig, weil sonst pro Smoke ein Device + Access-Token in der Synapse-DB hängenbleibt (über ein Jahr wären das ~50 000 Devices pro Tenant). - Bei Fehler: Zähler hochzählen. Erst nach 2 Fehlern in Folge geht eine Mail an Ops — verhindert Spam bei einmaligen Netzwerk-Glitches.
- Bei Erholung (nach Fehler-Serie wieder ok): eine „Recovered"-Mail.
Last-Messung produktiv: ~590 ms wall-time für alle vier Tenants pro Tick — also etwa eine Sekunde Aktivität pro Stunde. Auf die CPU schlägt das nicht messbar durch.
Heilungs-Routine: was tun, wenn Login wieder stirbt
Die Symptome zur Erkennung:
- User-Meldung: „Matrix-Token ungültig oder abgelaufen" oder „Benutzername oder Passwort falsch" trotz korrekter Eingabe.
pm2 logs backend-apizeigtINVALID_MATRIX_TOKEN-Antworten.- Cron-Mail vom
JWT-Login-Watchist eingegangen.
Schritt-für-Schritt-Heilung:
# 1. Aktueller stable-Stand
bash /var/www/backend-api/scripts/promote-connector.sh --current
# 2. Verfügbare Versionen
bash /var/www/backend-api/scripts/promote-connector.sh --list
# 3. Eine bekannt-gute Version promoten (Sanity-Check schützt vor falscher Wahl)
bash /var/www/backend-api/scripts/promote-connector.sh <sha>
# 4. Backend-Cache leeren
pm2 restart backend-api
# 5. Rollout auf alle Tenants — Smoke + Rollback laufen automatisch pro Tenant
cd /var/www/backend-api && npx tsx scripts/reconcile-tenant.ts --allFalls das nichts hilft (Connector ok, aber Login schlägt trotzdem fehl), prüfen:
- Passwort-Hash in der Tabelle
matrix_user_passwords: möglicherweise veraltet. Mitbcrypt.hash(plainPassword, 12)neu setzen undfailed_attempts = 0, locked_until = null. - Rate-Limit: nach zu vielen Fehlversuchen sperrt das Backend den Account temporär. Gleiche Tabelle, gleiches Feld.
- Synapse-Container läuft?
docker ps --filter name=synapse-<slug>— Statushealthyerwartet. - Bind-Mount sichtbar?
docker exec synapse-<slug> python -c 'import prilog_matrix_connector; print(prilog_matrix_connector.__file__)'mussok: /modules/...ausgeben.
Diagnose-Befehle
# Stable-Symlink prüfen
ls -la /var/www/prilog-artifacts/matrix-connector/prilog-matrix-connector-stable.tar.gz
# Welche Connector-Version läuft pro Tenant?
ssh root@<host> "docker exec synapse-<slug> grep -c register_password_auth_provider_callbacks /modules/prilog-matrix-connector/src/prilog_matrix_connector/module.py"
# Erwartet: >= 1
# Smoke-Login direkt gegen Synapse (mit echtem JWT-Secret aus tenant_settings)
# - bei lebendem Connector + ungültigem JWT: HTTP 400 oder M_FORBIDDEN
# - bei totem Connector: M_UNKNOWN "Unknown login type m.login.password"
curl -X POST "https://<slug>.prilog.team/_matrix/client/v3/login" \
-H 'content-type: application/json' \
-d '{"type":"m.login.password","identifier":{"type":"m.id.user","user":"admin"},"password":"x"}'
# Reconcile-Dry-Run (zeigt aktuellen Drift-Stand pro Tenant, ohne zu fixen)
cd /var/www/backend-api && npx tsx scripts/reconcile-tenant.ts --tenant=<slug> --dry-run
# Login-Watch manuell triggern (für Verifikation nach Eingriff)
cd /var/www/backend-api && npx tsx -e "import('./src/services/login-watch.service.js').then(m => m.runLoginWatch().then(console.log))"Was bei neuen Tenants automatisch greift
Beim Provisioning eines neuen Tenants:
synapse-jwt-provision.servicelegtsynapse_jwt_secretintenant_settingsan und schreibt dasmodules:-Block in diehomeserver.yaml.- Der Connector wird aus dem stable-Channel installiert (Fallback latest, falls noch nie promoted).
- Die Provisioning-Smoke-Suite (
tenant-smoke/index.ts) prüft sechs Connector-relevante Probes, darunterconnector_module_importableundsynapse_jwt_login_works. Schlägt eine pflicht-Probe fehl, ist das Provisioning rot. - Nach Provisioning übernimmt der 30-Min-Watch.
Heißt: Ein neuer Tenant geht überhaupt nur dann live, wenn der JWT-Login einmal funktioniert hat.
Referenz: betroffene Dateien
| Datei | Zweck |
|---|---|
prilog-backend-api/scripts/promote-connector.sh | Connector-Version als stable freigeben (mit Sanity-Check) |
prilog-backend-api/src/services/tenant-reconcile/connector-artifact.ts | Erwarteten Connector-Hash + Sanity-Check liefern |
prilog-backend-api/src/services/tenant-reconcile/fix-operations.ts | installMatrixConnector mit Smoke + Rollback |
prilog-backend-api/src/services/ops-alert.service.ts | Zentrale Ops-Mail mit 10-Min-Dedup |
prilog-backend-api/src/services/login-watch.service.ts | Periodischer JWT-Login-Health-Check |
prilog-backend-api/src/core/cron/jobs.ts (Eintrag jwt-login-watch) | Cron-Registrierung |
prilog-matrix-connector/.github/workflows/build.yml | CI mit JWT-Hook-Sanity-Check + Push in prilog-artifacts |
Verwandte Doku
- Synapse JWT-only Login — warum es überhaupt einen JWT-Pfad gibt.
- Tenant-Spec & Reconcile — wie der Drift-Detector grundsätzlich arbeitet.
- Komponenten-Release-Watch — wann ein Upstream-Release einen Reconcile auslöst.