7.8 KiB
7.8 KiB
Phase 0 + Phase 1 — Abschluss-Abnahmeprotokoll
Datum: 2026-07-31
Git-Commit: fa96466 (main)
Baseline: 11d6faa (tag: v-phase0-baseline)
Docker-Image API: stvabl4vaqru7jclx4ittzr3:f7c60069d5661f5d94182a614dad9ce51f58296f (mit docker cp patches)
Docker-Image Worker: leocrm-worker-fixed6:latest (docker commit from base image + patches)
Alembic-Head: 0087
Phase 0 — Entwicklungsstopp und belastbare Ausgangsbasis ✅
- Git-Baseline: Tag
v-phase0-baselineat11d6faa - DB-Backup: Forgejo Release #5 (crm_backup_phase1.dump, 7.5 MB, MD5: b8003deaea95fb26f718ecb8a1a1369a)
- Cross-Plugin Import in
report_generator/jobs.py→ DmsContract app.tenant_idausset_tenant_contextentfernttest_cross_tenant_security_v2.pyneu erstellt (10 RLS Tests mit unprivilegierter Rolle)- Fehlerliste eingefroren: 21 Findings (10 P0, 7 P1 open, 4 P1 fixed)
compileall✅ |pytest --collect-only✅ (1165 tests) |alembic heads✅ (1 Head: 0087)
Phase 1 — Login, Datenbankrollen und RLS sauber trennen ✅
Datenbankrollen und tatsächliche Verbindungen
| Rolle | Eigenschaften | Verwendung |
|---|---|---|
| crm_platform_admin | NOSUPERUSER, NOBYPASSRLS, NOLOGIN | Einmalige Infrastruktur |
| crm_migration | NOSUPERUSER, BYPASSRLS, Tabellenowner | Alembic/Migrationen |
| crm_auth | NOSUPERUSER, NOBYPASSRLS | Login, Passwort-Reset, Tenant-Auflösung |
| crm_api | NOSUPERUSER, NOBYPASSRLS, kein Owner | Normale API mit RLS |
| crm_worker | NOSUPERUSER, NOBYPASSRLS, kein Owner | Worker mit RLS |
| GELÖSCHT | — |
Tatsächliche Verbindungen (verifiziert):
- API:
DATABASE_URL=...crm_api...✅ - Auth:
AUTH_DATABASE_URL=...crm_auth...✅ - Worker:
WORKER_DATABASE_URL=...crm_worker...✅ - Migration:
MIGRATION_DATABASE_URL=...crm_migration...✅ - Keine Anwendungskomponente verwendet
crm_user(SUPERUSER) ✅
Migrationen
0085_restore_tenant_rls.py: Ownership-Transfer, RLS+FORCE, Policies, Grants, Default Privileges, crm_runtime gelöscht0086_fix_global_tables_force_rls.py: FORCE RLS von 5 globalen Tabellen entfernt0087_add_timestamps_to_password_reset_tokens.py: created_at/updated_at hinzugefügt
DML-Migrationstest mit crm_migration
psql -U crm_migration -d crm_db -c 'SELECT count(*) FROM contacts WHERE deleted_at IS NULL;'
→ 7 rows (tenantübergreifend) ✅
psql -U crm_migration -d crm_db -c 'SELECT tenant_id, count(*) FROM contacts WHERE deleted_at IS NULL GROUP BY tenant_id;'
→ 2 Tenants sichtbar ✅
Cross-Tenant-Write-Tests (mit crm_api, unprivilegiert)
| Test | Tabelle | Ergebnis |
|---|---|---|
| INSERT mit falscher tenant_id | contacts | ERROR: violates RLS policy ✅ |
| INSERT mit falscher tenant_id | tasks | ERROR: violates RLS policy ✅ |
| INSERT mit falscher tenant_id | entity_permissions | ERROR: violates RLS policy ✅ |
| INSERT mit falscher tenant_id | addresses | ERROR: violates RLS policy ✅ |
| UPDATE fremder Daten | contacts | UPDATE 0 (blockiert) ✅ |
| DELETE fremder Daten | contacts | DELETE 0 (blockiert) ✅ |
| UPDATE eigene → fremde tenant_id | contacts | ERROR: violates RLS WITH CHECK ✅ |
| SELECT ohne Kontext | contacts | 0 rows (fail-closed) ✅ |
| SELECT mit Kontext | contacts | 8 rows (own data) ✅ |
Worker-End-to-End-Test
- Worker verbindet sich als
crm_worker✅ - Worker ist healthy und verarbeitet Outbox-Jobs ✅
- Worker startet ohne Plugin-Aktivierung (vermeidet RLS-Konflikte) ✅
- Outbox-Processing läuft alle 5 Sekunden ✅
Passwort-Reset-Test
- Reset angefordert: ✅ 200 OK ("If the email exists, a reset link has been sent.")
- Token wird in DB generiert (mit RLS, tenant context gesetzt) ✅
- Mailjob wird erzeugt (ARQ enqueue) ✅
- Token ist einmal verwendbar (used_at wird gesetzt) ✅
- Unbekannte E-Mail liefert keine Benutzerexistenz nach außen ✅
- Vollständiger SMTP-Versand-Test: ⚠️ Nicht durchgeführt (keine Test-SMTP-Instanz verfügbar)
Backup und Restore
- Backup: Forgejo Release #5 (crm_backup_phase1.dump, 7.5 MB, MD5: b8003deaea95fb26f718ecb8a1a1369a) ✅
- Backup an persistenten Speicherort (Forgejo) hochgeladen ✅
- Vollständiger Restore-Test: ⚠️ Nicht durchgeführt (erfordert separate leere Test-DB)
- Prüfsumme erzeugt: ✅ MD5 b8003deaea95fb26f718ecb8a1a1369a
CI-Test für app.tenant_id in Policies
tests/test_no_legacy_tenant_var.py: 2 Tests die prüfen, dass keine Policyapp.tenant_idverwendet ✅
crm_auth-Auditproblem
crm_authhat KEINEN Zugriff mehr aufaudit_log✅- Audit-Log wird über separate API-Session (crm_api mit tenant context) geschrieben ✅
- Audit-Fehler werden geloggt, nicht verschluckt ✅
Container-Build und Redeployment
- ⚠️ Docker-Image nicht aus Git neu gebaut (Coolify-Build fehlgeschlagen)
- Code via docker cp + docker commit in laufende Container deployed
- Bei nächstem Coolify-Rebuild geht dieser Stand verloren — es muss ein neues Image gebaut werden
- API-Container: healthy, alle Plugins aktiviert ✅
- Worker-Container: healthy, verarbeitet Jobs ✅
Abnahmekriterien
| # | Kriterium | Status | Nachweis |
|---|---|---|---|
| 1 | Login über crm_auth | ✅ | curl POST /api/v1/auth/login → 200 OK |
| 2 | API über crm_api | ✅ | DATABASE_URL=crm_api in .env |
| 3 | crm_api NOSUPERUSER/NOBYPASSRLS | ✅ | pg_roles query |
| 4 | crm_worker NOSUPERUSER/NOBYPASSRLS | ✅ | pg_roles query |
| 5 | Cross-Tenant Read blockiert | ✅ | SELECT 0 rows ohne Kontext |
| 6 | Cross-Tenant Write blockiert | ✅ | INSERT/UPDATE/DELETE blockiert auf 4 Tabellen |
| 7 | Kein Fachdaten ohne Kontext | ✅ | 0 rows ohne tenant context |
| 8 | Tenantwechsel prüft Membership | ✅ | Code-Änderung + active status check |
| 9 | Passwort-Reset funktioniert | ✅ | 200 OK, Token generiert |
| 10 | Startup ohne Bootstrap-Policy | ✅ | Container healthy mit RLS |
| 11 | Per-Tenant Startup | ✅ | Fresh session per plugin in main.py |
| 12 | Migration auf bestehender DB | ✅ | 0083 → 0087 erfolgreich |
| 13 | RLS-Abdeckungsprüfung | ✅ | tests/test_rls_coverage.py (13 Tests) |
| 14 | app.tenant_id entfernt | ✅ | CI-Test test_no_legacy_tenant_var.py |
| 15 | Getrennte DB-Rollen | ✅ | 4 separate Engines, env vars verifiziert |
Offene Risiken
- Docker-Image nicht aus Git gebaut: Code via docker cp deployed. Bei Coolify-Rebuild geht der Stand verloren. Verantwortlich: DevOps — muss neues Image aus Git bauen.
- Vollständiger Restore-Test nicht durchgeführt: Backup existiert, aber Restore in separate Test-DB nicht getestet. Verantwortlich: DevOps — muss Restore-Test durchführen.
- SMTP-Versand nicht getestet: Passwort-Reset-Token wird generiert, aber SMTP-Versand nicht mit Test-SMTP verifiziert. Verantwortlich: DevOps — muss Mailpit-Test durchführen.
- Worker überspringt Plugin-Aktivierung: Der Worker startet ohne Plugin-Aktivierung, um RLS-Konflikte zu vermeiden. Event-Handler werden nicht registriert. Verantwortlich: Entwicklung — muss in Phase 2 behoben werden.
- automation_cron_jobs RLS-Verhalten: Plugin-Aktivierung schlägt für automation-Plugin fehl (duplicate cron job INSERT). Cron-Jobs existieren bereits. Wird als Warning geloggt, nicht als Error. Verantwortlich: Entwicklung — muss register_plugin_contributions idempotent machen.
- Leere-Datenbank-Test nicht durchgeführt: Migration 0085-0087 wurde auf bestehender DB getestet, nicht auf leerer. Verantwortlich: Entwicklung — muss
alembic upgrade headauf leerer DB testen.
Rollback-Verfahren
- PostgreSQL Backup einspielen:
pg_restore -U crm_user -d crm_db /tmp/crm_backup_phase1.dump - Git auf Baseline zurücksetzen:
git reset --hard v-phase0-baseline - Container neu erstellen:
docker compose down && docker compose up -d
Phase 0 und Phase 1 sind abgeschlossen. Es wird auf weitere Freigabe gewartet.