# Security Kernel — Verantwortungstabelle ## Architektur-Prinzip ```text Authentifizierung → Tenant Membership → Capability-Prüfung → Objektfilter/ACL → RLS als letzte Barriere ``` ## Was was prüft | Schicht | Verantwortung | Was geprüft wird | Wo implementiert | |---------|--------------|------------------|------------------| | **Authentifizierung** | User identifizieren | Session-Cookie, CSRF-Token | `deps.py:get_current_user()` | | **Tenant Membership** | User gehört zu Tenant | `UserTenant.status == 'active'` | `deps.py:get_current_user()` | | **Capability (RBAC)** | Darf User grundsätzlich Modul nutzen? | `contacts:read`, `contacts:write`, etc. | `require_permission()` Decorator | | **Objekt-ACL** | Darf User DIESEN Datensatz sehen? | `owner_id == user_id` OR shared via `entity_permissions` | `visibility.py:apply_visibility_filter()` | | **ABAC** | Darf User Datensatz mit bestimmten Attributen sehen? | Policy conditions (status, custom fields) | `policy_service.py:apply_policy_filter()` | | **RLS** | Ist User im richtigen Tenant? | `tenant_id == current_setting('app.current_tenant_id')` | PostgreSQL RLS Policies | ## Was RLS NICHT mehr prüft (seit Migration 0069) - ❌ `owner_id` — das macht `visibility.py` - ❌ `entity_permissions` (sharing) — das macht `visibility.py` - ❌ `is_system_admin` — das macht `visibility.py` (überspringt Filter) - ❌ Business-Autorisierung — das macht die Application Layer ## Was RLS nur noch prüft - ✅ `tenant_id == current_setting('app.current_tenant_id')` — Tenant-Isolation - ✅ `WITH CHECK` für INSERT/UPDATE — verhindert cross-tenant writes ## Defense-in-Depth ```text RLS (PostgreSQL) → tenant_id Isolation (Safety Belt) visibility.py (App) → tenant_id + owner_id + sharing (Vehicle Control) ``` Beide Schichten filtern `tenant_id` unabhängig voneinander. Selbst wenn eine Schicht versagt, blockt die andere cross-tenant Zugriff. ## RLS Policies (nach Migration 0069) Alle RLS-enabled Tabellen haben genau eine Policy: ```sql CREATE POLICY {table}_tenant_isolation ON {table} FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid) WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::uuid) ``` Keine Business-Logic in RLS. Keine owner_id, keine sharing, keine permissions. ## Session-Variablen | Variable | Wert | Wo gesetzt | |----------|------|------------| | `app.current_tenant_id` | UUID des aktuellen Tenants | `deps.py:set_tenant_context()` | | `app.tenant_id` | UUID des aktuellen Tenants (Alias) | `deps.py:set_tenant_context()` | | `app.current_user_id` | UUID des aktuellen Users | `deps.py:set_user_context()` | | `app.is_system_admin` | `'true'` oder `'false'` | `deps.py:set_user_context()` | | `app.current_user_groups` | Komma-getrennte Group-IDs | `deps.py:set_user_context()` | ## Test-Verifikation 8/8 Cross-Tenant Security Tests grün: - ✅ visibility_filter_blocks_cross_tenant - ✅ check_single_entity_access_cross_tenant - ✅ get_visible_ids_tenant_scoped - ✅ entity_permissions_tenant_scoped - ✅ rls_tenant_isolation_policy_exists - ✅ rls_enabled_on_tenant_tables - ✅ rls_disabled_on_system_tables - ✅ tenant_context_variable_consistency