386 lines
29 KiB
Markdown
386 lines
29 KiB
Markdown
# Enterprise RBAC, Scope Enforcement & Navigation Governance Audit
|
|
|
|
**Datum:** 9. März 2026
|
|
**Anwendung:** TenantPilot / TenantAtlas
|
|
**Stack:** Laravel 12, Filament v5, Livewire v4, PostgreSQL, Tailwind v4
|
|
**Auditor:** Enterprise SaaS Security & Architecture Audit
|
|
**Scope:** RBAC, Multi-Tenancy, Scope Enforcement, Navigation Governance, Filament Authorization
|
|
|
|
---
|
|
|
|
## 1. Executive Security / Structure Assessment
|
|
|
|
### Gesamturteil: **Teilweise robust — mit gezieltem Hardening-Bedarf**
|
|
|
|
Die Anwendung verfügt über ein **architektonisch durchdachtes, capability-first RBAC-Modell** mit klarer Trennung von Platform/Workspace/Tenant-Scopes. Die Kernarchitektur (CapabilityResolver, UiEnforcement, Membership-Manager, Policies) ist solide und enterprise-geeignet.
|
|
|
|
Allerdings gibt es **strukturelle Lücken** in folgenden Bereichen:
|
|
- System-Panel-Widgets aggregieren cross-workspace/cross-tenant Daten ohne Kompartimentierung
|
|
- Einige Monitoring-Pages fehlen explizite `canAccess()` Checks und verlassen sich ausschließlich auf Middleware
|
|
- AuditLog-Modell hat keine `workspace_id` Spalte und damit kein strukturelles Workspace-Scoping
|
|
- Einige Modelle (OperationRun, BaselineProfile, AlertRule) haben keine Immutabilitäts-Traits am Model-Level
|
|
- URL-basierte Enumeration von sequenziellen IDs ist möglich (Finding, OperationRun)
|
|
|
|
### Die 5 kritischsten RBAC-/Scope-Probleme
|
|
|
|
| # | Problem | Severity |
|
|
|---|---------|----------|
|
|
| 1 | **System Panel leakt Cross-Workspace/Cross-Tenant Operational Data** — ControlTower-Widgets zeigen Workspace-Namen, Tenant-Namen und Fehlerstatistiken aller Mandanten an jeden PlatformUser | CRITICAL |
|
|
| 2 | **Directory-Pages exponieren alle Tenants/Workspaces** — System/Directory-Seiten erlauben Aufzählung aller registrierten Tenants/Workspaces mit Health-Status und Permission-Gaps | HIGH |
|
|
| 3 | **AuditLog hat kein strukturelles Workspace-Scoping** — Nur `tenant_id`, kein `workspace_id`; Scoping ist rein auf Query-Level und kann bei neuen Zugangspfaden umgangen werden | HIGH |
|
|
| 4 | **Monitoring-Pages (Operations, AuditLog) fehlen `canAccess()` Checks** — Verlassen sich nur auf Middleware + Query-Scoping, nicht auf explizite Page-Level Authorization | MEDIUM |
|
|
| 5 | **Sequenzielle IDs in URLs erlauben Enumeration** — Finding-IDs, OperationRun-IDs sind sequential integers in Deep Links und Notifications | MEDIUM |
|
|
|
|
### Die 5 wichtigsten Korrekturen
|
|
|
|
| # | Maßnahme | Priorität |
|
|
|---|----------|-----------|
|
|
| 1 | System-Panel Kompartimentierung: Capability-Granularisierung für cross-workspace Sicht | P0 |
|
|
| 2 | `canAccess()` auf allen Monitoring-Pages implementieren | P1 |
|
|
| 3 | AuditLog-Modell um `workspace_id` ergänzen mit Migration + Scoping-Trait | P1 |
|
|
| 4 | OperationRun + BaselineProfile + AuditLog mit DerivesWorkspaceId-Trait absichern | P2 |
|
|
| 5 | UUID-basierte Route-Keys für sensitive Ressourcen (Finding, OperationRun) evaluieren | P2 |
|
|
|
|
---
|
|
|
|
## 2. Access Model Map
|
|
|
|
### Subjekte
|
|
|
|
| Subjekt | Guard | Scope | Modell |
|
|
|---------|-------|-------|--------|
|
|
| **Platform User** | `platform` | System-weit | `PlatformUser` |
|
|
| **Workspace Owner** | `web` | Workspace | `User` + `WorkspaceMembership(role=owner)` |
|
|
| **Workspace Manager** | `web` | Workspace | `User` + `WorkspaceMembership(role=manager)` |
|
|
| **Workspace Operator** | `web` | Workspace | `User` + `WorkspaceMembership(role=operator)` |
|
|
| **Workspace Readonly** | `web` | Workspace | `User` + `WorkspaceMembership(role=readonly)` |
|
|
| **Tenant Owner** | `web` | Tenant | `User` + `TenantMembership(role=owner)` |
|
|
| **Tenant Manager** | `web` | Tenant | `User` + `TenantMembership(role=manager)` |
|
|
| **Tenant Operator** | `web` | Tenant | `User` + `TenantMembership(role=operator)` |
|
|
| **Tenant Readonly** | `web` | Tenant | `User` + `TenantMembership(role=readonly)` |
|
|
|
|
### Scopes
|
|
|
|
| Scope | Kontext | Enforcement |
|
|
|-------|---------|-------------|
|
|
| **Platform** | System-Administration (break-glass, directory, ops) | `platform` Guard + PlatformCapabilities |
|
|
| **Workspace** | Organisation/Account-Isolation | WorkspaceMembership + WorkspaceCapabilityResolver + WorkspaceContext (Session) |
|
|
| **Tenant** | Mandant innerhalb eines Workspace | TenantMembership + CapabilityResolver + Filament::getTenant() |
|
|
|
|
### Kernobjekte pro Scope
|
|
|
|
| Scope | Objekte |
|
|
|-------|---------|
|
|
| **Platform** | PlatformUser, Access Logs, Break-Glass-Sessions |
|
|
| **Workspace** | Workspace, WorkspaceMembership, WorkspaceSetting, AlertRule, AlertDestination, BaselineProfile, OperationRun (multi-tenant) |
|
|
| **Tenant** | Tenant, TenantMembership, Policy, PolicyVersion, BackupSet, BackupItem, BackupSchedule, RestoreRun, Finding, ReviewPack, InventoryItem, EntraGroup, ProviderConnection, TenantPermission, TenantRoleMapping |
|
|
| **Ambivalent** | AuditLog (nur tenant_id, kein workspace_id), OperationRun (hat workspace_id, aber kein Immutabilitäts-Trait), AlertDelivery (workspace_id + optional tenant_id) |
|
|
|
|
### Capability-Muster
|
|
|
|
**Klar und konsistent:**
|
|
- Capability-first Design über `Capabilities::*` und `PlatformCapabilities::*`
|
|
- Static Registry (`Capabilities::all()`, `Capabilities::isKnown()`)
|
|
- Rolle→Capability Mapping über `RoleCapabilityMap` und `WorkspaceRoleCapabilityMap`
|
|
- Gates werden dynamisch aus Capabilities registriert (AuthServiceProvider)
|
|
- UiEnforcement erzwingt dreilagige Autorisierung (Visibility → Disabled → Server Guard)
|
|
|
|
**Inkonsistent:**
|
|
- Einige Policies nutzen `Gate::allows()`, andere nutzen `CapabilityResolver::can()` direkt
|
|
- System-Panel nutzt separate `PlatformCapabilities` ohne Kompartimentierung innerhalb des System-Scopes
|
|
- AuditLog hat keinen eigenen Capability-Check — Sichtbarkeit wird durch Page-Navigation gesteuert
|
|
|
|
---
|
|
|
|
## 3. Findings Table
|
|
|
|
| ID | Severity | Kategorie | Datei / Klasse | Scope | UI-Symptom | Technische Ursache | Risiko | Empfohlene Korrektur |
|
|
|----|----------|-----------|----------------|-------|------------|---------------------|--------|---------------------|
|
|
| F-01 | **CRITICAL** | Cross-Scope Data Leak | `App\Filament\System\Widgets\ControlTowerTopOffenders` | System→All | Widget zeigt Top-10-Fehler mit Workspace+Tenant-Namen | Query ohne jegliche Scope-Filterung: `OperationRun::query()->selectRaw('workspace_id, tenant_id...')` | Jeder PlatformUser sieht Betriebsdaten aller Mandanten | Capability-basierte Kompartimentierung oder Anonymisierung im System-Panel |
|
|
| F-02 | **CRITICAL** | Cross-Scope Data Leak | `App\Filament\System\Widgets\ControlTowerKpis` | System→All | KPI-Dashboard zeigt Gesamtstatistiken über alle Workspaces | Globale `OperationRun::query()` ohne WHERE-Klausel | Informations-Leaking über Betriebsumfang und Fehlerquoten | Scope-Filter oder Capability-Granularisierung |
|
|
| F-03 | **CRITICAL** | Cross-Scope Data Leak | `App\Filament\System\Widgets\ControlTowerRecentFailures` | System→All | Zeigt letzte fehlgeschlagene Runs mit Workspace-Namen | Ungefilterter Query | Sensitive Betriebs-Metadaten sichtbar | Scope-aware oder anonymisiert |
|
|
| F-04 | **HIGH** | Tenant Enumeration | `App\Filament\System\Pages\Directory\Tenants` | System | Directory listet alle Tenants mit Health, Status, Permission Gaps | `Tenant::query()->with('workspace')` ohne Scope | Reconnaissance: Angreifer sieht alle Mandanten, deren Konfigurationsprobleme und Provider-Status | Capability-basierter Zugang oder Scope-Einschränkung |
|
|
| F-05 | **HIGH** | Workspace Enumeration | `App\Filament\System\Pages\Directory\Workspaces` | System | Directory listet alle Workspaces mit Tenant-Count und Fehlerquoten | `Workspace::query()` ohne Scope | Reconnaissance: vollständige Sicht auf Struktur und Probleme aller Organisationen | Wie F-04 |
|
|
| F-06 | **HIGH** | Missing Structural Scope | `App\Models\AuditLog` | Tenant/Workspace | AuditLog-Einträge haben kein `workspace_id` | Model hat nur `tenant_id`, kein Workspace-Feld | Bei neuen Zugangspfaden oder Queries ohne Tenant-Kontext könnten Audit-Daten cross-workspace leaken | Migration: `workspace_id` Spalte + NOT NULL + DerivesWorkspaceIdFromTenant Trait |
|
|
| F-07 | **MEDIUM** | Missing `canAccess()` | `App\Filament\Pages\Monitoring\Operations` | Admin | Seite ist über `/admin/operations` direkt erreichbar | Kein expliziter `canAccess()` Check, nur Middleware+Query-Scoping | Falls Middleware fehlschlägt oder umgangen wird, keine zweite Schutzschicht | `canAccess()` mit WorkspaceCapabilityResolver implementieren |
|
|
| F-08 | **MEDIUM** | Missing `canAccess()` | `App\Filament\Pages\Monitoring\AuditLog` | Admin | Seite über `/admin/audit-log` erreichbar, `shouldRegisterNavigation=false` | Kein `canAccess()`, nur `shouldRegisterNavigation=false` und OperateHubShell | Hidden page ohne Page-Level Auth ist per URL erreichbar | `canAccess()` implementieren |
|
|
| F-09 | **MEDIUM** | URL Enumeration | Notification Deep Links + Route `{run}` | Tenant | OperationRun-IDs in Notification-URLs sind sequential integers | `OperationRunLinks::tenantlessView($run)` nutzt Integer-ID | ID-Enumeration erlaubt Erkennung valider Run-IDs (Auth verhindert Lesezugriff) | UUID-basierte Route-Keys oder Rate-Limiting |
|
|
| F-10 | **MEDIUM** | URL Enumeration | Finding-URLs in Tenant Panel | Tenant | Finding-IDs in Links: `/findings/140`, `/findings/139` | Sequential integer keys in URLs | Ähnlich wie F-09 | UUID-basierte Anzeige |
|
|
| F-11 | **MEDIUM** | No Model Immutability | `App\Models\OperationRun` | Workspace/Tenant | N/A | Kein `DerivesWorkspaceIdFromTenant` Trait; workspace_id wird per boot() auto-filled, aber nicht immutabel erzwungen | Theoretische Reassignment-Möglichkeit (kein aktueller Exploit-Pfad) | DerivesWorkspaceIdFromTenant Trait hinzufügen |
|
|
| F-12 | **MEDIUM** | No Model Immutability | `App\Models\BaselineProfile` | Workspace | N/A | Kein Scoping-Trait; workspace_id muss bei Creation gesetzt werden | Developer-Fehler bei zukünftigen Code-Änderungen möglich | Trait oder DB-Constraint |
|
|
| F-13 | **LOW** | Break-Glass Scope | `App\Filament\System\Pages\RepairWorkspaceOwners` | System | PlatformUser kann Owner für beliebigen Workspace zuweisen | Nur `USE_BREAK_GLASS` Capability geprüft, keine Workspace-Zugehörigkeitsvalidierung | Intentional (Emergency-Feature), aber unkompartimentiert | Dokumentation und Audit-Alerting sicherstellen; optional: Require 4-eyes |
|
|
| F-14 | **LOW** | Access Log Exposure | `App\Filament\System\Pages\Security\AccessLogs` | System | Zeigt alle Platform-Logins und Break-Glass-Aktivitäten | Nur `CONSOLE_VIEW` Capability erforderlich | Offenlegen, wer System-Zugang hat und wann | Separate Capability `SECURITY_AUDIT_VIEW` |
|
|
| F-15 | **LOW** | Livewire Auth Gap | `App\Livewire\SettingsCatalogSettingsTable` | Tenant | Liest Policy-Einstellungen ohne eigene Auth | Kein eigener Authorization-Check; angenommen: nur auf autorisierten Seiten gemounted | Falls auf anderer Seite gemounted, Daten-Leak möglich | Defensive Auth prüfen oder Assert bei mount() |
|
|
|
|
---
|
|
|
|
## 4. Mismatch Matrix
|
|
|
|
| Dimension | Status | Details |
|
|
|-----------|--------|---------|
|
|
| **Navigation Visibility ↔ Policy Enforcement** | ✅ Konsistent für Tenant-Panel | Navigation items nutzen `canViewAny()` + Policy-Checks; UiEnforcement erzwingt Dreischicht-Modell |
|
|
| **Navigation Visibility ↔ Policy Enforcement** | ⚠️ Inkonsistent für Monitoring-Pages | Operations und AuditLog sind `shouldRegisterNavigation=false` aber per URL erreichbar ohne `canAccess()` |
|
|
| **Navigation Visibility ↔ Policy Enforcement** | ⚠️ Inkonsistent für System-Panel | System-Pages haben nur `DIRECTORY_VIEW`/`OPERATIONS_VIEW` Capability, aber zeigen Daten aller Mandanten |
|
|
| **Query/Data Scope ↔ Navigation Scope** | ✅ Konsistent für Tenant-scoped Resources | `getEloquentQuery()` filtert konsequent nach `Tenant::current()` |
|
|
| **Query/Data Scope ↔ Navigation Scope** | ✅ Konsistent für Workspace-scoped Resources | `getEloquentQuery()` filtert nach `WorkspaceContext::currentWorkspaceId()` mit `whereRaw('1 = 0')` als Fallback |
|
|
| **Query/Data Scope ↔ Navigation Scope** | ⚠️ System-Widgets: Kein Scope, Navigation erlaubt | ControlTower-Widgets haben weder Query-Scope noch Navigation-Gate |
|
|
| **Route Accessibility ↔ Authorization** | ✅ Tenant Panel Seiten | Middleware-Stack (`ensure-workspace-selected`, `ensure-filament-tenant-selected`, `DenyNonMemberTenantAccess`) |
|
|
| **Route Accessibility ↔ Authorization** | ✅ Tenantless Run Viewer | `mount()` → `$this->authorize('view', $run)` → OperationRunPolicy prüft Workspace+Tenant+Capability |
|
|
| **Route Accessibility ↔ Authorization** | ⚠️ AuditLog Page | `shouldRegisterNavigation=false`, aber kein `canAccess()` — per URL erreichbar |
|
|
| **Action Auth ↔ Bulk Action Auth** | ✅ Konsistent | UiEnforcement erzwingt all-or-nothing Semantik für Bulk Actions mit gleicher Capability wie Single Actions |
|
|
| **Widget Data ↔ User Scope** | ✅ Dashboard-Widgets | `DashboardKpis`, `NeedsAttention`, `RecentOperations` nutzen `Filament::getTenant()` korrekt |
|
|
| **Widget Data ↔ User Scope** | ✅ AlertsKpiHeader | Filtert nach workspace_id + user.tenantMemberships + optional activeTenant |
|
|
| **Widget Data ↔ User Scope** | ⚠️ ControlTower-Widgets | Keine Scope-Filterung — zeigen Platform-weite Aggregate |
|
|
| **Global Search ↔ Scope** | ✅ ScopesGlobalSearchToTenant Trait | Prüft `canAccessTenant()` und filtert per `whereBelongsTo()` |
|
|
| **Global Search ↔ Scope** | ⚠️ Trait-Nutzung verifizieren | Unklar ob alle globalsuchbaren Resources diesen Trait nutzen |
|
|
| **Relation Managers ↔ Parent Scope** | ✅ Konsistent | TenantMembershipsRelationManager, BackupItemsRelationManager etc. erben Parent-Scope des owning Resource |
|
|
| **Deep Links ↔ Auth** | ✅ Notification-URLs | OperationRunPolicy prüft Workspace+Tenant bei mount() |
|
|
| **Deep Links ↔ Auth** | ⚠️ IDs in URLs | Sequential integers ermöglichen Enumeration |
|
|
|
|
---
|
|
|
|
## 5. Target Enterprise RBAC Model
|
|
|
|
### Rollen-/Capability-Modell (Zielzustand)
|
|
|
|
```
|
|
Platform Layer
|
|
├── Platform Admin: Full system access
|
|
├── Platform Ops: Operations monitoring (scoped)
|
|
├── Platform Audit: Security audit view only
|
|
└── Platform Support: Break-glass + Directory (audited, 4-eyes for critical)
|
|
|
|
Workspace Layer
|
|
├── Owner: Full workspace control + all tenant capabilities
|
|
├── Manager: Workspace admin (no archive/delete) + most tenant capabilities
|
|
├── Operator: Execute operations + view
|
|
└── Readonly: View-only
|
|
|
|
Tenant Layer
|
|
├── Owner: Full tenant control
|
|
├── Manager: Admin (no delete/membership manage)
|
|
├── Operator: Execute sync/backup/run + view
|
|
└── Readonly: View-only
|
|
```
|
|
|
|
### Regeln für Navigation Visibility
|
|
|
|
1. **Navigation zeigt NUR Items, für die der Nutzer authorization hat** — sowohl `shouldRegisterNavigation()` als auch `canAccess()` müssen konsistent sein
|
|
2. **Hidden Pages müssen `canAccess()` implementieren** — `shouldRegisterNavigation=false` bedeutet nicht "keine Autorisierung nötig"
|
|
3. **System-Panel Navigation muss capability-granular sein** — nicht eine einzige `CONSOLE_VIEW` Capability für alles
|
|
4. **Navigation darf niemals die einzige Schutzschicht sein**
|
|
|
|
### Regeln für Query Scope
|
|
|
|
1. **Jede `getEloquentQuery()` muss scope-aware sein** — mit `whereRaw('1 = 0')` als Fallback bei fehlendem Kontext
|
|
2. **Tenant-scoped Modelle nutzen `DerivesWorkspaceIdFromTenant` oder `$tenantOwnershipRelationshipName`**
|
|
3. **Workspace-scoped Modelle filtern nach `WorkspaceContext::currentWorkspaceId()`**
|
|
4. **System-Queries (ControlTower) müssen entweder scope-kompartimentiert oder anonymisiert sein**
|
|
5. **AuditLog muss `workspace_id` als strukturelles Scope-Feld haben**
|
|
|
|
### Regeln für Direct URL Access
|
|
|
|
1. **Jede Page muss `canAccess()` implementieren** — keine Ausnahme für "hidden" Pages
|
|
2. **Route Model Binding muss Scope-Ownership erzwingen** — OperationRunPolicy-Muster als Standard
|
|
3. **UUID-basierte Route-Keys für sensitive Ressourcen** — Findings, OperationRuns
|
|
4. **Rate-Limiting auf Detail-Endpoints** — gegen Enumeration
|
|
|
|
### Regeln für Actions / Bulk Actions
|
|
|
|
1. **UiEnforcement ist der Standard für alle Actions** — kein Bypass
|
|
2. **Destructive Actions: `->requiresConfirmation()` + `->action(...)` + Capability-Check**
|
|
3. **Bulk Actions: all-or-nothing Semantik (bereits implementiert)**
|
|
4. **Keine Action darf schwächer autorisiert sein als die entsprechende Single-Record-Action**
|
|
|
|
### Regeln für Widgets, Search, Exports und Deep Links
|
|
|
|
1. **Widgets dürfen nur scope-konforme Daten anzeigen** — kein Count über unerlaubte Scopes
|
|
2. **Global Search muss `ScopesGlobalSearchToTenant` nutzen** — für alle tenant-scoped Resources
|
|
3. **Deep Links in Notifications müssen Policy-geschützt sein** — bereits implementiert, UUID-Keys evaluieren
|
|
4. **Export-Funktionen müssen dieselbe Scope-Filterung wie die UI anwenden**
|
|
|
|
---
|
|
|
|
## 6. Hardening Recommendations
|
|
|
|
### Quick Wins (P0-P1, sofort umsetzbar)
|
|
|
|
| # | Maßnahme | Nutzen | Risiko | Aufwand | Priorität |
|
|
|---|----------|--------|--------|---------|-----------|
|
|
| QW-1 | `canAccess()` auf `Operations.php` implementieren — WorkspaceCapabilityResolver + Membership Check | Schließt Page-Level Auth Lücke | Minimal: bestehende Pattern kopieren | 1h | P1 |
|
|
| QW-2 | `canAccess()` auf `AuditLog.php` implementieren | Wie QW-1 | Minimal | 1h | P1 |
|
|
| QW-3 | System-Panel Capability-Granularisierung: `OPERATIONS_VIEW` in `OPS_VIEW_RUNS`, `OPS_VIEW_FAILURES`, `OPS_VIEW_STUCK` aufteilen | Feinere Zugriffskontrolle | Migration + PlatformUser Capability-Update | 4h | P1 |
|
|
| QW-4 | ControlTower-Widgets: Option für anonymisierte/aggregierte Ansicht | Verhindert Tenant-Namen-Leak | Widget-Refactor | 4h | P1 |
|
|
| QW-5 | `SettingsCatalogSettingsTable` Livewire: Defensive Auth in `mount()` | Verhindert Context-Leak bei Wiederverwendung | Minimal | 30min | P2 |
|
|
|
|
### Mittlere Hardening-Maßnahmen (P2, geplant)
|
|
|
|
| # | Maßnahme | Nutzen | Risiko | Aufwand | Priorität |
|
|
|---|----------|--------|--------|---------|-----------|
|
|
| MH-1 | AuditLog Migration: `workspace_id` Spalte + NOT NULL + Index | Strukturelles Workspace-Scoping | Backfill-Migration nötig | 8h | P2 |
|
|
| MH-2 | `DerivesWorkspaceIdFromTenant` auf OperationRun-Model | Immutabilitäts-Garantie | Sehr gering | 2h | P2 |
|
|
| MH-3 | `DerivesWorkspaceIdFromTenant` auf BaselineProfile-Model | Wie MH-2 | Gering | 2h | P2 |
|
|
| MH-4 | UUID-basierte Route-Keys für Finding, OperationRun | Verhindert ID-Enumeration | Model+Migration+Route-Refactor | 12h | P2 |
|
|
| MH-5 | Rate-Limiting auf `/admin/operations/{run}` und ähnliche Detail-Endpoints | Anti-Enumeration | Middleware-Config | 2h | P2 |
|
|
| MH-6 | System-Panel Directory: Capability `DIRECTORY_MANAGE` vs `DIRECTORY_VIEW` differenzieren | Least-Privilege im System-Panel | Capability-Registry Update | 4h | P2 |
|
|
| MH-7 | Break-Glass: 4-eyes-Prinzip oder temporal scoping verschärfen | Privileged-Access-Governance | UX-Design nötig | 8h | P2 |
|
|
|
|
### Strukturelle Refactors (P3, Spec-gesteuert)
|
|
|
|
| # | Maßnahme | Nutzen | Risiko | Aufwand | Priorität | Spec? |
|
|
|---|----------|--------|--------|---------|-----------|-------|
|
|
| SR-1 | Global Scope auf OperationRun für automatisches Workspace-Scoping | Präventiv gegen zukünftige Query-Fehler | Model-Behavior-Änderung; Tests nötig | 16h | P3 | Ja |
|
|
| SR-2 | System-Panel Kompartimentierung: Scope-basierte Platform Views | Enterprise isolation für managed-service Betrieb | Architektur-Refactor | 40h | P3 | Ja |
|
|
| SR-3 | Einheitliche `canAccess()` Base-Mixin für alle Pages | DRY + Konsistenz | Base-Class Refactor | 16h | P3 | Ja |
|
|
| SR-4 | Monitoring-Pages: Unified OperateHub Authorization Layer | Konsolidierte Auth statt Page-for-Page | Architektur | 24h | P3 | Ja |
|
|
|
|
---
|
|
|
|
## 7. Project Rules
|
|
|
|
### Verbindliche Enterprise RBAC Rules
|
|
|
|
1. **Navigation ist nie die Sicherheitsgrenze.** Jede Filament-Page muss eine `canAccess()` Methode implementieren, die unabhängig von Navigation Visibility autorisiert. `shouldRegisterNavigation=false` ersetzt keinen Authorization-Check.
|
|
|
|
2. **Jede sichtbare Aktion braucht deckungsgleiche Server-Autorisierung.** UiEnforcement (Tenant) oder WorkspaceUiEnforcement (Workspace) ist der Standard-Wrapper für alle Actions. Kein Action-Handler darf ohne Server-Side Guard ausgeführt werden.
|
|
|
|
3. **Hidden Pages dürfen nicht unautorisiert per URL erreichbar sein.** Jede Page mit `shouldRegisterNavigation=false` oder `isDiscovered=false` MUSS explizit `canAccess()` implementieren.
|
|
|
|
4. **Workspace- und Tenant-Scope müssen in Query, Route Binding und Policy konsistent erzwungen werden.** Scope-Enforcement findet auf mindestens zwei Ebenen statt: Query-Level (`getEloquentQuery()`) UND Policy/Gate-Level.
|
|
|
|
5. **Widgets, Counts und Search dürfen keine scope-fremden Metadaten leaken.** Jedes Dashboard-Widget muss denselben Scope-Filter anwenden wie die zugehörige Tabelle/Resource. System-Widgets müssen scope-kompartimentiert oder anonymisiert sein.
|
|
|
|
6. **Bulk Actions dürfen nie schwächer autorisiert sein als Single Actions.** UiEnforcement erzwingt all-or-nothing Semantik: Wenn ein Record in der Selektion die Capability verletzt, wird die gesamte Bulk-Action blockiert.
|
|
|
|
7. **Deep Links und Notifications müssen kanonisch und permission-sicher sein.** Jede URL, die per Notification, E-Mail oder API-Response geteilt wird, muss bei Zugriff denselben Auth-Check durchlaufen wie der UI-Pfad.
|
|
|
|
8. **Route Model Binding muss Scope-Ownership erzwingen.** Policies müssen bei `view()` immer Workspace-Membership UND Tenant-Membership (falls tenant-scoped) prüfen. Non-Members erhalten 404 (deny-as-not-found), nicht 403.
|
|
|
|
9. **Capability-first vor rollen-/UI-getriebener Logik.** Autorisierung leitet sich aus Capabilities ab, nicht aus Rollen-Strings oder UI-Zustand. `RoleCapabilityMap` ist die Single Source of Truth.
|
|
|
|
10. **Kein Zugriff über "zufällig funktionierende" indirekte Pfade.** Jeder neue Pfad (Relation Manager, Tab, Widget, Global Search, Export) muss explizit scope-geprüft werden. Filament Auto-Discovery allein ist keine Autorisierung.
|
|
|
|
11. **Modelle mit Tenant- oder Workspace-Zugehörigkeit MÜSSEN Immutabilitäts-Traits nutzen.** `DerivesWorkspaceIdFromTenant` oder vergleichbare Traits stellen sicher, dass Scope-Zuweisungen nach Creation nicht verändert werden können.
|
|
|
|
12. **System-Panel Capabilities müssen granular sein.** Eine einzige `CONSOLE_VIEW` Capability für cross-workspace Sichtbarkeit reicht für Enterprise-Betrieb nicht aus. Differenzierung in Operations, Directory, Security, Break-Glass.
|
|
|
|
13. **Destructive Actions erfordern immer `->requiresConfirmation()`.** Keine Ausnahme, auch nicht bei "schnellen" Aktionen wie Archive oder Toggle.
|
|
|
|
14. **AuditLog muss workspace-scoped sein.** Jeder Audit-Eintrag muss `workspace_id` tragen, um cross-workspace Reporting und Compliance zu ermöglichen.
|
|
|
|
---
|
|
|
|
## 8. Spec Proposal Backlog
|
|
|
|
### Spec A: System Panel Kompartimentierung
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | System Panel Capability Granularization & Data Compartmentalization |
|
|
| **Problem** | Alle PlatformUser mit `CONSOLE_VIEW` sehen Betriebsdaten aller Workspaces/Tenants. ControlTower-Widgets, Directory-Pages und Access-Logs bieten keine Scope-Isolation. |
|
|
| **Ziel** | PlatformCapabilities aufteilen in `OPS_VIEW_RUNS`, `OPS_VIEW_FAILURES`, `DIRECTORY_VIEW_TENANTS`, `DIRECTORY_VIEW_WORKSPACES`, `SECURITY_AUDIT_VIEW`. ControlTower-Widgets anonymisieren oder scope-kompartimentieren. |
|
|
| **Scope** | System Panel Widgets, Directory Pages, Access Logs, PlatformCapabilities Registry |
|
|
| **Non-goals** | Nicht: Umbau der web-Guard-Architektur; nicht: Multi-Panel Redesign |
|
|
| **Priorität** | P1 — Enterprise-Compliance-Risiko |
|
|
| **Abhängigkeiten** | Keine |
|
|
|
|
### Spec B: Page-Level Authorization Enforcement
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | Enforce canAccess() on all Filament Pages |
|
|
| **Problem** | Mehrere Pages (Operations, AuditLog, potentiell weitere) haben keinen expliziten `canAccess()` Check und verlassen sich nur auf Middleware |
|
|
| **Ziel** | Alle Pages im Admin/Tenant/System Panel implementieren `canAccess()` mit CapabilityResolver/WorkspaceCapabilityResolver |
|
|
| **Scope** | Alle Filament Pages; PHPStan oder Pest-Regel zur Enforcement-Prüfung |
|
|
| **Non-goals** | Nicht: Middleware-Umbau; nicht: UiEnforcement-Refactor |
|
|
| **Priorität** | P1 — Defense-in-Depth |
|
|
| **Abhängigkeiten** | Keine |
|
|
|
|
### Spec C: AuditLog Workspace Scoping
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | Add workspace_id to AuditLog for Structural Scope Isolation |
|
|
| **Problem** | AuditLog hat nur `tenant_id`, kein `workspace_id`. Query-Level-Scoping ist fragil und kann bei neuen Zugangspfaden umgangen werden. |
|
|
| **Ziel** | Migration: `workspace_id` Spalte (nullable → later NOT NULL); Backfill-Job; DerivesWorkspaceIdFromTenant-Trait; Index |
|
|
| **Scope** | AuditLog Model, Migration, Backfill, Audit-Service Updates |
|
|
| **Non-goals** | Nicht: AuditLog Schema-Redesign; nicht: Retention-Policy |
|
|
| **Priorität** | P2 |
|
|
| **Abhängigkeiten** | Keine |
|
|
|
|
### Spec D: Model Immutability Enforcement
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | Apply DerivesWorkspaceId Trait to OperationRun, BaselineProfile, AlertRule |
|
|
| **Problem** | OperationRun, BaselineProfile, AlertRule haben keine Model-Level Immutabilitäts-Garantie für workspace_id. Scope-Zuweisungen könnten theoretisch nach Creation verändert werden. |
|
|
| **Ziel** | DerivesWorkspaceIdFromTenant oder DerivesWorkspaceIdFromTenantWhenPresent Trait auf alle scope-relevanten Modelle anwenden; WorkspaceIsolationViolation als Guard |
|
|
| **Scope** | OperationRun, BaselineProfile, AlertRule, AlertDestination Models |
|
|
| **Non-goals** | Nicht: Global Scope Enforcement (separate Spec) |
|
|
| **Priorität** | P2 |
|
|
| **Abhängigkeiten** | Keine |
|
|
|
|
### Spec E: UUID Route Keys für Sensitive Resources
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | UUID-based Route Keys for Findings, OperationRuns, and Sensitive Records |
|
|
| **Problem** | Sequential integer IDs in URLs ermöglichen Enumeration. Notification-Deep-Links und Tabellen-Links enthalten vorhersagbare IDs. |
|
|
| **Ziel** | getRouteKeyName() auf `external_id` (UUID) umstellen für Finding, OperationRun; Migration für UUID-Spalte; URL-Refactor |
|
|
| **Scope** | Finding, OperationRun, ReviewPack Models; Routes; Notification Links |
|
|
| **Non-goals** | Nicht: Alle Models umstellen; nicht: bestehende DB-PKs ändern |
|
|
| **Priorität** | P2 |
|
|
| **Abhängigkeiten** | Keine |
|
|
|
|
### Spec F: OperateHub Unified Authorization Layer
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | Unified Authorization Layer for Monitoring/OperateHub Pages |
|
|
| **Problem** | Monitoring-Pages (Operations, Alerts, AuditLog) haben unterschiedliche Auth-Patterns. Operations nutzt kein `canAccess()`, Alerts nutzt WorkspaceCapabilityResolver, AuditLog nutzt keines. |
|
|
| **Ziel** | Shared Trait oder Base-Page-Klasse für OperateHub-Pages mit einheitlicher `canAccess()` + Capability-Mapping |
|
|
| **Scope** | OperateHub Pages, Monitoring Cluster, Shared Trait |
|
|
| **Non-goals** | Nicht: Redesign der OperateHubShell Navigation-Logik |
|
|
| **Priorität** | P2 |
|
|
| **Abhängigkeiten** | Spec B (Page-Level Auth Enforcement) |
|
|
|
|
### Spec G: Global Search Scope Verification & Trait Coverage
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| **Titel** | Verify and Enforce ScopesGlobalSearchToTenant Usage on All Searchable Resources |
|
|
| **Problem** | `ScopesGlobalSearchToTenant` Trait existiert und ist gut implementiert, aber es ist unklar, ob alle global-suchbaren Resources ihn nutzen. |
|
|
| **Ziel** | Audit: Welche Resources sind global-suchbar? Nutzen alle den Trait? Pest-Test für Enforcement. |
|
|
| **Scope** | Alle Filament Resources mit $recordTitleAttribute oder globallySearchable |
|
|
| **Non-goals** | Nicht: Redesign der Global-Search-Architektur |
|
|
| **Priorität** | P2 |
|
|
| **Abhängigkeiten** | Keine |
|
|
|
|
---
|
|
|
|
## Appendix: Positive Architecture Highlights
|
|
|
|
### Was gut funktioniert
|
|
|
|
1. **Capability-first Design** mit `Capabilities::all()` als Single Source of Truth und dynamischer Gate-Registrierung
|
|
2. **UiEnforcement** als dreilagiger RBAC-Wrapper (Visibility → Disabled → Server Guard) — Enterprise-Grade
|
|
3. **Deny-as-not-found Semantik** — Non-Members bekommen 404, nicht 403 (verhindert Existence-Leaking)
|
|
4. **DerivesWorkspaceIdFromTenant Trait** — Model-Level Immutabilität mit `WorkspaceIsolationViolation` Exception
|
|
5. **Last-Owner Guard** — Verhindert Removal/Demotion des letzten Workspace/Tenant Owners
|
|
6. **Workspace 7-Step Selection Algorithm** — Robust mit Stale-Session-Handling und Audit-Events
|
|
7. **Tenant Middleware Stack** — 4 Middleware-Layer (correct guard → workspace selected → tenant selected → deny non-member)
|
|
8. **Break-Glass Auditing** — Vollständig auditiert mit TTL, explicit start/exit, IP-Logging
|
|
9. **OperateHubShell** — Konsistente Cross-Tenant-Sicht mit Membership-Validation und Entitlement-Prüfung
|
|
10. **WriteGateInterface** — Hardening-Layer gegen unsichere RBAC-Zustände bei Intune-Writes
|
|
|
|
### Zusammenfassung
|
|
|
|
Die Architektur zeigt ein **reifes, mehrschichtiges Autorisierungsmodell**, das für die meisten Enterprise-Szenarien robust ist. Die identifizierten Lücken betreffen primär:
|
|
|
|
1. **System-Panel**: Unzureichende Kompartimentierung (alle PlatformUser sehen alles)
|
|
2. **Monitoring-Pages**: Fehlende explizite Page-Level Auth (Defense-in-Depth Lücke)
|
|
3. **AuditLog**: Fehlendes strukturelles Workspace-Scoping
|
|
4. **ID-Enumeration**: Sequenzielle IDs in URLs (kein Datenleak, aber Reconnaissance-Vektor)
|
|
|
|
Keines dieser Findings ermöglicht aktuell einen direkten Daten-Leak oder Privilege-Escalation über die bestehenden Middleware- und Policy-Checks hinaus. Die Maßnahmen dienen der **Enterprise-Härtung und Defense-in-Depth**, nicht der Schließung aktiver Sicherheitslücken.
|