phase1: RLS simplified to tenant isolation only + canAccess fallback removed + useUserPermissions hook + security kernel docs

This commit is contained in:
Agent Zero
2026-07-29 16:36:51 +02:00
parent 66fd387301
commit 8da803156e
9 changed files with 222 additions and 16 deletions
+74
View File
@@ -0,0 +1,74 @@
# 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