645 lines
32 KiB
Markdown
645 lines
32 KiB
Markdown
# Feature Specification: TenantPilot v1
|
||
|
||
**Feature Branch**: `tenantpilot-v1`
|
||
**Created**: 2025-12-10
|
||
**Status**: Draft
|
||
**Input**: TenantPilot v1 scope covering Intune configuration inventory (config, compliance, scripts, apps, conditional access, endpoint security, enrollment/autopilot, RBAC), backup, version history, and defensive restore for Intune administrators.
|
||
|
||
## Scope
|
||
|
||
```yaml
|
||
scope:
|
||
description: "v1 muss folgende Intune-Objekttypen inventarisieren, sichern und – je nach Risikoklasse – wiederherstellen können."
|
||
supported_types:
|
||
- key: deviceConfiguration
|
||
name: "Device Configuration"
|
||
graph_resource: "deviceManagement/deviceConfigurations"
|
||
notes: "Inklusive Custom OMA-URI, Administrative Templates und Settings Catalog."
|
||
|
||
- key: deviceCompliancePolicy
|
||
name: "Device Compliance"
|
||
graph_resource: "deviceManagement/deviceCompliancePolicies"
|
||
|
||
- key: appProtectionPolicy
|
||
name: "App Protection (MAM)"
|
||
graph_resource: "deviceAppManagement/managedAppPolicies"
|
||
notes: "iOS und Android Managed App Protection."
|
||
|
||
- key: conditionalAccessPolicy
|
||
name: "Conditional Access"
|
||
graph_resource: "identity/conditionalAccess/policies"
|
||
notes: "Kritisch für Sicherheit. Policy.Read.All/Policy.ReadWrite.All nötig; v1: Restore nur mit starker Preview."
|
||
|
||
- key: deviceManagementScript
|
||
name: "PowerShell Scripts"
|
||
graph_resource: "deviceManagement/deviceManagementScripts"
|
||
notes: "scriptContent wird beim Backup base64-decoded gespeichert und beim Restore wieder encoded (vgl. FR-020)."
|
||
|
||
- key: enrollmentRestriction
|
||
name: "Enrollment Restrictions"
|
||
graph_resource: "deviceManagement/deviceEnrollmentConfigurations"
|
||
|
||
- key: windowsAutopilotDeploymentProfile
|
||
name: "Windows Autopilot Profiles"
|
||
graph_resource: "deviceManagement/windowsAutopilotDeploymentProfiles"
|
||
|
||
- key: windowsEnrollmentStatusPage
|
||
name: "Enrollment Status Page (ESP)"
|
||
graph_resource: "deviceManagement/deviceEnrollmentConfigurations"
|
||
filter: "odata.type eq '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration'"
|
||
|
||
- key: endpointSecurityIntent
|
||
name: "Endpoint Security Intents"
|
||
graph_resource: "deviceManagement/intents"
|
||
notes: "Account Protection, Disk Encryption etc.; Zuordnung über bekannte Templates."
|
||
|
||
- key: mobileApp
|
||
name: "Applications (Metadata only)"
|
||
graph_resource: "deviceAppManagement/mobileApps"
|
||
notes: "Backup nur von Metadaten/Zuweisungen (kein Binary-Download in v1)."
|
||
|
||
restore_matrix:
|
||
deviceConfiguration:
|
||
backup: full
|
||
restore: enabled
|
||
risk: medium
|
||
notes: "Standard-Case für Backup+Restore; starke Preview/Audit Pflicht."
|
||
|
||
deviceCompliancePolicy:
|
||
backup: full
|
||
restore: enabled
|
||
risk: medium
|
||
notes: "Compliance-Änderungen können Zugriff beeinflussen, aber sind gut verständlich."
|
||
|
||
appProtectionPolicy:
|
||
backup: full
|
||
restore: enabled
|
||
risk: medium-high
|
||
notes: "MAM-Änderungen wirken auf Datenzugriff in Apps; Preview und Diff wichtig."
|
||
|
||
conditionalAccessPolicy:
|
||
backup: full
|
||
restore: preview-only
|
||
risk: high
|
||
notes: "Hohe Ausfallgefahr. v1: Backup, Versioning, Diff + ausführliche Preview; Restore nur manuell anhand Preview."
|
||
|
||
deviceManagementScript:
|
||
backup: full
|
||
restore: enabled
|
||
risk: medium
|
||
notes: "Script-Inhalt und Einstellungen werden gesichert; Decode/Encode beachten."
|
||
|
||
enrollmentRestriction:
|
||
backup: full
|
||
restore: preview-only
|
||
risk: high
|
||
notes: "Kann Enrollment blockieren; v1 eher nur Preview + manuelle Umsetzung."
|
||
|
||
windowsAutopilotDeploymentProfile:
|
||
backup: full
|
||
restore: enabled
|
||
risk: medium-high
|
||
notes: "Provisioning-kritisch; Preview + Audit, aber automatisierbar."
|
||
|
||
windowsEnrollmentStatusPage:
|
||
backup: full
|
||
restore: enabled
|
||
risk: medium
|
||
notes: "ESP beeinflusst OOBE UX; Änderungen klar sichtbar."
|
||
|
||
endpointSecurityIntent:
|
||
backup: full
|
||
restore: enabled
|
||
risk: high
|
||
notes: "Security-relevante Einstellungen (z. B. Credential Guard); Preview + klare Konflikt-Warnungen nötig."
|
||
|
||
mobileApp:
|
||
backup: metadata-only
|
||
restore: enabled
|
||
risk: low-medium
|
||
notes: "Nur Metadaten/Zuweisungen; kein Binary; Restore setzt Konfigurationen/Zuweisungen wieder."
|
||
```
|
||
|
||
## User Scenarios & Testing *(mandatory)*
|
||
|
||
### User Story 1 - Policy inventory listing (Priority: P1)
|
||
|
||
Admin can view supported Intune object types (as defined in the scope) with normalized metadata for selection.
|
||
|
||
**Why this priority**: Inventory is the entry point for backup/version flows. Without it, no downstream workflows are usable.
|
||
|
||
**Independent Test**: From Filament, navigate to Policies; verify supported types render with identifiers, type/category, platform metadata, and tenant scoping.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** an authenticated admin, **When** they open the Policies list, **Then** they see supported object types with identifiers, type/category, platform, and last-updated metadata.
|
||
2. **Given** filtering by type/category, **When** the admin selects a type, **Then** only matching objects appear and the view remains tenant-scoped.
|
||
|
||
---
|
||
### User Story 1b - Policy detail shows readable settings (Priority: P1)
|
||
|
||
Admin can open a policy detail page and see the **effective Intune settings** in a readable, normalized way (not raw JSON dumps).
|
||
|
||
**Independent Test**: From Filament, open a policy detail view; verify a "Settings" section renders normalized key/value pairs (or tables for special cases) derived from the latest snapshot.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a policy with at least one captured snapshot, **When** the admin opens the policy detail view, **Then** they see a "Settings" section rendering the policy configuration in a readable format (grouped/labeled).
|
||
2. **Given** the snapshot contains nested structures or list-based settings (e.g., OMA-URI / Settings Catalog), **When** the admin views settings, **Then** values are flattened/grouped or rendered as tables, and irrelevant metadata keys are hidden.
|
||
---
|
||
### User Story 2 - Backup creation and browsing (Priority: P1)
|
||
|
||
Admin creates backup sets containing multiple objects (config, compliance, scripts, apps, CA, etc.) with immutable snapshots and can browse backup details in Filament.
|
||
|
||
**Why this priority**: Backups provide safety and enable restore; immutability and audit are foundational.
|
||
|
||
**Independent Test**: Initiate a backup set selecting multiple objects; confirm immutable JSONB snapshots persisted, audit log written, and Filament shows backup detail and items.
|
||
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** selected objects from different categories, **When** the admin creates a backup set, **Then** backup items store immutable payload snapshots (full or metadata-only as per the restore matrix) with identifiers and types.
|
||
2. **Given** a completed backup set, **When** the admin opens its detail page, **Then** all items and metadata display along with the audit record of creation.
|
||
|
||
3. **Given** mehrere Backup-Sets existieren,
|
||
**When** der Admin ein Backup-Set auswählen oder ansehen möchte,
|
||
**Then** sieht er für jedes Set:
|
||
- einen sprechenden Namen (nicht nur Timestamp),
|
||
- das Erstellungsdatum,
|
||
- die Anzahl der enthaltenen Items,
|
||
- und optional eine kurze Beschreibung, damit er das Set sinnvoll unterscheiden kann.
|
||
|
||
---
|
||
|
||
### User Story 3 - Version history and diff (Priority: P1)
|
||
|
||
Admin can capture versions for any supported object, view timelines, and compare any two versions with meaningful diffs.
|
||
|
||
**Why this priority**: Version visibility and diffs enable rollback readiness and change comprehension.
|
||
|
||
**Independent Test**: Create multiple versions for a given object; verify timeline ordering, version metadata, and diff output (human summary + JSON diff where feasible) between any two versions.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** an admin triggers version capture for an object, **When** the version is saved, **Then** an immutable snapshot and metadata (actor, time, type, tenant) are recorded.
|
||
2. **Given** two versions of the same object, **When** the admin requests a comparison, **Then** the UI shows a human-readable summary and structured JSON diff where available.
|
||
3. **Given** a saved policy version, **When** the admin opens the version detail page, **Then** the snapshot is displayed as pretty-printed JSON and, where possible, as normalized settings (not as an unreadable serialized array/string).
|
||
---
|
||
|
||
### User Story 4 - Restore with preview and confirmation (Priority: P1)
|
||
|
||
Admin can run a restore from a backup set with preview/dry-run, selective restore, clear warnings, and required confirmation before execution.
|
||
|
||
**Why this priority**: Restore is high-risk; safety features are mandatory for production readiness.
|
||
|
||
**Independent Test**: Start a restore from a backup set in preview; view change summary and warnings; select items; confirm execution; verify audit logs and outcomes recorded (success/failure/partial).
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a backup set, **When** the admin initiates a restore in preview mode, **Then** the system shows a change summary with selectable items and conflict warnings.
|
||
2. **Given** selected items and explicit confirmation, **When** execution proceeds, **Then** applied changes are tenant-scoped and audit logs record start, result, and any failures.
|
||
|
||
3. **Given** mehrere Backup-Sets existieren,
|
||
**When** der Admin einen Restore Run erstellt,
|
||
**Then** zeigt die Auswahl für das "Backup set" mindestens:
|
||
- den Backup-Namen,
|
||
- das Erstellungsdatum,
|
||
- die Anzahl der Items,
|
||
damit der Admin das richtige Backup-Set sicher auswählen kann.
|
||
|
||
4. **Given** ein Restore Run wurde erstellt,
|
||
**When** der Admin die Detailseite des Restore Runs öffnet,
|
||
**Then** sieht er, welche Policies/Items in diesem Run enthalten sind
|
||
(z. B. Liste der Policies mit Name/Typ/Plattform).
|
||
---
|
||
|
||
### User Story 5 - Operational readiness and environments (Priority: P2)
|
||
|
||
Local development uses Sail; deployments target Dokploy staging then production with clear validation steps.
|
||
|
||
**Why this priority**: Ensures reproducible local setup and safe promotion to production.
|
||
|
||
**Independent Test**: Run the app locally via Sail; validate migrations on staging before production; confirm required env vars and queues/workers are documented.
|
||
|
||
|
||
|
||
|
||
### User Story 6 - Berechtigungsübersicht & Health-Status (Priority: P1)
|
||
|
||
Als Admin möchte ich für jeden Tenant sehen, welche Microsoft Graph-Berechtigungen
|
||
erforderlich sind, welche bereits erteilt wurden und welche fehlen, damit ich
|
||
sicherstellen kann, dass alle Funktionen von TenantPilot sicher und vollständig
|
||
arbeiten.
|
||
|
||
**Why this priority**: Jede neue Funktion kann zusätzliche Berechtigungen benötigen.
|
||
Ohne transparente Übersicht und Abgleich besteht das Risiko, dass Features still
|
||
kaputt sind oder unsicher laufen.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** ein Tenant ist in TenantPilot hinterlegt,
|
||
**When** der Admin die Tenant-Detailseite öffnet,
|
||
**Then** sieht er eine Liste aller *erforderlichen* Berechtigungen mit Status
|
||
(z. B. OK, fehlt).
|
||
|
||
2. **Given** neue Funktionen wurden eingeführt, die zusätzliche Berechtigungen benötigen
|
||
und diese wurden in der zentralen Permissions-Liste hinzugefügt,
|
||
**When** der Admin die Tenant-Detailseite öffnet,
|
||
**Then** erscheinen die neuen Berechtigungen automatisch in der Übersicht und
|
||
fehlende Berechtigungen werden klar als fehlend markiert.
|
||
|
||
3. **Given** der Admin klickt auf "Verify configuration",
|
||
**When** TenantPilot einen Graph-Twestcall und/oder das Permission-Setup prüft,
|
||
**Then** wird der Status der Berechtigungen aktualisiert (OK/fehlt/Fehler) und
|
||
es wird ein Audit-Eintrag erstellt.
|
||
|
||
4. **Given** ein Tenant hat fehlende kritische Berechtigungen,
|
||
**When** andere Features (Policy-Sync, Backup, Restore) diesen Tenant verwenden,
|
||
**Then** kann TenantPilot dem Admin entsprechende Warnungen anzeigen oder die
|
||
Funktion mit einem klaren Fehler abbrechen.
|
||
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a fresh checkout, **When** Sail commands run (`./vendor/bin/sail up -d`, `./vendor/bin/sail artisan migrate`), **Then** the app boots with PostgreSQL and Filament admin available.
|
||
2. **Given** a pending release, **When** migrations and restore flows are validated on staging, **Then** production deployment proceeds with documented steps and environment parity.
|
||
|
||
### Edge Cases
|
||
|
||
- Graph permissions missing or expired, causing policy fetch/restore failures with clear error mapping and audit entries.
|
||
- Large policy payloads or many items in a backup set; ensure JSONB storage and pagination handle load without timeouts.
|
||
- Restore conflicts when target tenant already has newer versions; preview must surface warnings and allow skip.
|
||
- Partial restore failures; audits must capture per-item outcomes and surface retry guidance.
|
||
- Diff generation for incompatible or malformed payloads should fail gracefully with admin-facing messaging.
|
||
- Retention/size concerns for snapshots; document defaults and guard against unbounded growth.
|
||
- Snapshots stored as serialized strings or array-only dumps (keys lost) must be detected; UI should show a clear warning and fall back to raw display.
|
||
- Policies whose `@odata.type` does not match the expected platform/type mapping should be flagged to prevent wrong restore previews (e.g., stored as Windows but snapshot indicates Android).
|
||
## Requirements *(mandatory)*
|
||
|
||
### Functional Requirements
|
||
|
||
- **FR-001**: System MUST list all Intune objects defined in the `scope.supported_types` section with normalized metadata and tenant scoping for selection.
|
||
- **FR-002**: System MUST allow admins to create backup sets containing multiple objects (configuration, compliance, scripts, apps, conditional access, etc.) with immutable JSONB payload snapshots.
|
||
- **FR-003**: Backup creation MUST log audit events including actor, timestamp, tenant, items, and outcome.
|
||
- **FR-004**: System MUST capture policy versions on demand and present per-policy timelines.
|
||
- **FR-005**: Users MUST be able to diff any two versions with a human-readable summary and structured JSON diff where feasible.
|
||
- **FR-006**: Restore MUST support preview/dry-run, selective item restore, and explicit confirmation before applying changes, within the per-type restore level defined in `scope.restore_matrix`.
|
||
- **FR-007**: Restore execution MUST produce audit logs covering success, failure, and partial outcomes.
|
||
- **FR-008**: Graph integration MUST route through a dedicated abstraction layer with standardized error mapping, safe retries, and high-level logging without secrets.
|
||
- **FR-009**: All policy, version, backup, and restore data MUST be tenant-aware; queries enforce tenant isolation.
|
||
- **FR-010**: Application MUST run locally via Laravel Sail with PostgreSQL and provide Filament admin flows.
|
||
- **FR-011**: Deployments MUST target Dokploy staging before production with documented migration and worker implications.
|
||
- **FR-012**: Tests MUST cover backup composition rules, version immutability, audit events, and Filament backup/restore flows (with Graph boundaries mocked).
|
||
- **FR-013**: Raw policy snapshots and backup payloads MUST be stored as JSONB with indexes justified by query needs (e.g., FK and time-based; GIN when filters require).
|
||
- **FR-014**: UI MUST provide clear warnings for potential restore conflicts and require confirmation for destructive operations; for types with `restore: preview-only` in `scope.restore_matrix` no direct apply action MAY be offered.
|
||
- **FR-015**: Admins MUST be able to safely delete (archive) backup sets that are no
|
||
longer needed. Deletion is implemented as soft-delete with audit logging, and
|
||
backup sets referenced by completed restore runs cannot be removed.
|
||
|
||
- **FR-016**: Admins MUST be able to delete individual policy versions for housekeeping.
|
||
Deletion is implemented as soft-delete with audit logging.
|
||
|
||
- **FR-017**: Admins MUST be able to deactivate (soft-delete) a tenant.
|
||
Deactivated
|
||
tenants:
|
||
- do not appear in default lists,
|
||
- cannot be used for new sync/backup/restore operations,
|
||
- keep their historical data and audit logs for traceability.
|
||
- **FR-018**: Admins MAY soft-delete restore runs to keep the UI clean; underlying
|
||
backup and policy data remains untouched.
|
||
|
||
- **FR-019**: The system MUST normalize different payload structures for display via a `PolicyNormalizer` (or equivalent): OMA-URI/custom policies as path/value tables, Settings Catalog policies as flattened structures, and standard objects as key-value views, aligned with `scope.supported_types`.
|
||
- **FR-019a**: Policy detail views MUST display a "Settings" section derived from the latest available snapshot (using the normalizer output when available).
|
||
- **FR-019b**: Policy version detail views MUST render snapshots as pretty-printed JSON (monospace, copyable) and SHOULD also render normalized settings via the same normalizer.
|
||
- **FR-020**: For PowerShell script objects (`deviceManagementScript` in `scope.supported_types`), the `scriptContent` MUST be base64-decoded when stored in backups/versions for readability/diffing and encoded again when sent back to Graph during restore.
|
||
- **FR-021**: Restore behavior MUST follow the per-type configuration in `scope.restore_matrix`: `backup` determines full vs metadata-only snapshots; `restore` determines whether automated restore is enabled or preview-only; `risk` informs warning/confirmation UX.
|
||
- **FR-022**: For high-risk types with `restore: preview-only` in `scope.restore_matrix` (e.g., `conditionalAccessPolicy`, `enrollmentRestriction`), TenantPilot MUST provide full backups, version history, and diffs plus detailed restore previews, but MUST NOT expose direct Graph apply actions; restore is manual, guided by the preview.
|
||
|
||
### Key Entities *(include if feature involves data)*
|
||
|
||
- **tenants**: Represents the deployment tenant context; referenced by all scoped data.
|
||
- **policies**: Normalized metadata for supported Intune policies.
|
||
- **policy_versions**: Immutable snapshots with metadata (actor, timestamp, tenant, policy type).
|
||
- **backup_sets**: Group of backup items with creator, timestamp, and tenant context.
|
||
- **backup_items**: Individual policy snapshots within a backup set (immutable JSONB payload + identifiers).
|
||
- **restore_runs**: Execution records for restores, including preview/actual flags and outcomes.
|
||
- **audit_logs**: Audit trail entries for backups, restores, version captures, and significant Graph actions.
|
||
|
||
## Success Criteria *(mandatory)*
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-001**: Admin can create a backup set selecting multiple policies and view immutable backup items with audit logs in Filament.
|
||
- **SC-002**: Policy version history timeline is available per policy and supports comparing any two versions with summary and JSON diff outputs.
|
||
- **SC-003**: Restore preview shows change summaries and conflict warnings; execution requires explicit confirmation and produces audit logs for all outcomes.
|
||
- **SC-004**: Core flows run locally via Sail; staging validation of migrations and restore paths completes before production deployments.
|
||
- **SC-005**: Automated tests covering backup composition, version immutability, audit logging, and Filament backup/restore flows pass via `./vendor/bin/sail artisan test`.
|
||
|
||
|
||
### Technical Story – Enforce Single Current Tenant ("Highlander Principle")
|
||
|
||
**Context**
|
||
|
||
Aktuell können mehrere Tenants `status = active` sein. Graph-Operationen (Policy Sync,
|
||
Backup, Restore) wählen den Kontext über Heuristiken (`findOrCreateDefault`,
|
||
`local-tenant`), was zu falschen Tenants und Fehlern führt.
|
||
|
||
**Goal**
|
||
|
||
Es soll **immer genau einen klar definierten "current" Tenant** geben, über den
|
||
alle Graph-Operationen laufen. Die Auswahl dieses Tenants ist explizit und
|
||
transparent (UI + Env), nicht implizit.
|
||
|
||
**Requirements**
|
||
|
||
- Es gibt ein Flag `is_current` in `tenants`, das den aktuell verwendeten Kontext
|
||
markiert.
|
||
- Die Datenbank erzwingt per partiellem Unique Index, dass höchstens ein
|
||
nicht-gelöschter Tenant `is_current = true` haben kann.
|
||
- `Tenant::current()` liefert:
|
||
- falls `INTUNE_TENANT_ID` gesetzt ist, **genau diesen** Tenant (Fehler, wenn
|
||
er nicht existiert oder deaktiviert ist),
|
||
- sonst den Tenant mit `is_current = true` und `status = active`.
|
||
- falls keiner gefunden wird, eine klare Exception (“No current tenant selected”);
|
||
es werden keine Dummy-Tenants erzeugt.
|
||
- In der Tenant-Verwaltung gibt es eine Action "Make current", die:
|
||
- in einer Transaktion alle anderen Tenants auf `is_current = false` setzt
|
||
und den gewählten Tenant auf `is_current = true`,
|
||
- nur für aktive Tenants verfügbar ist.
|
||
- Der frühere Placeholder `local-tenant` darf nicht mehr als Graph-Kontext genutzt
|
||
werden; sobald ein echter Tenant existiert, wird er archiviert und ist nie
|
||
`is_current`.
|
||
- Alle Graph-basierten Funktionen (Policy Sync, Backup, Restore) verwenden
|
||
konsistent `Tenant::current()` oder einen explizit übergebenen Tenant.
|
||
|
||
Tenant-level actions such as "Admin consent" and "Verify configuration"
|
||
MUST be exposed on the tenant detail view (and/or row actions), not as a
|
||
global button without explicit tenant context.
|
||
|
||
|
||
### UX Guideline – Table Actions / Dropdowns
|
||
|
||
- Tabellen in Filament mit mehr als zwei Zeilen-Aktionen (z.B. View, Edit,
|
||
Admin consent, Verify, Deactivate, Force delete) MÜSSEN ihre Aktionen in
|
||
einem kompakten Dropdown / ActionGroup bündeln, statt alle Buttons nebeneinander
|
||
anzuzeigen.
|
||
- Ausnahmen: besonders häufige, nicht-destruktive Aktionen (z.B. "View")
|
||
dürfen weiterhin als einzelner Button sichtbar bleiben; alle weiteren
|
||
Aktionen (z.B. Admin-Aktionen, Housekeeping) sollen im Dropdown liegen.
|
||
- Ziel: die Tabellen bleiben übersichtlich, Spaltenbreite wird begrenzt,
|
||
und Admins bekommen eine konsistente "⋯"-Interaktion für erweiterte Aktionen.
|
||
|
||
|
||
|
||
## User Story 7 – Intune RBAC Onboarding Wizard (Delegated Admin Login) *(Priority: P1)*
|
||
|
||
### Problem / Context
|
||
|
||
TenantPilot arbeitet primär **app-only (Client Credentials)** gegen Microsoft Graph.
|
||
Für viele Intune-Objekte reicht „Graph App Permissions + Admin Consent“ allein nicht aus: Intune kann zusätzlich über **Intune RBAC**
|
||
blockieren, wenn der **Service Principal** (Enterprise App) keine passende **Intune Role Assignment** inkl. Scope hat.
|
||
Das äußert sich typischerweise als **403** mit „Application is not authorized to perform this operation“.
|
||
|
||
Dieses Setup ist ein **Bootstrap-Problem**:
|
||
- Ohne RBAC-Zuweisung sind Intune Reads/Writes blockiert.
|
||
- Ohne ausreichende Rechte kann TenantPilot die RBAC-Zuweisung nicht „self-service“ per app-only herstellen.
|
||
|
||
**Ziel:** TenantPilot bietet pro Tenant einen **Onboarding-Wizard**, bei dem ein Admin sich **interaktiv (delegated)** anmeldet,
|
||
und TenantPilot automatisiert (idempotent) die erforderliche Intune-RBAC-Konfiguration für die konfigurierte Enterprise App herstellt.
|
||
Danach funktionieren Policy Sync / Backup / Restore (gemäß Restore Matrix) zuverlässig.
|
||
|
||
---
|
||
|
||
### User Value
|
||
|
||
- Admins können RBAC-Probleme direkt in TenantPilot beheben (kein “Portal-Rätselraten”).
|
||
- Klarer, auditierter Ablauf (wer hat wann welche Rechte/Sopes gesetzt).
|
||
- Minimiert Ausfälle bei Policy-Sync/Backup/Restore und reduziert Support-Aufwand.
|
||
|
||
---
|
||
|
||
### In Scope (v1)
|
||
|
||
- Wizard in Filament auf der **Tenant-Detailseite** (tenant-scoped).
|
||
- Delegated Admin Login (interaktiv).
|
||
- Idempotente Ausführung:
|
||
- Service Principal (zu `tenant.app_client_id`) auflösen
|
||
- RBAC-Membership via **Security Group (recommended)** herstellen
|
||
- Intune Role Assignment erstellen/aktualisieren (Rolle + Scope)
|
||
- Abschließender Verify-Run (Health/Permissions aktualisieren)
|
||
- Vollständige Audit-Logs pro Step.
|
||
|
||
---
|
||
|
||
### Out of Scope (v1)
|
||
|
||
- Vollautomatisches “Self-heal” ohne Admin-Interaktion.
|
||
- Zeitgesteuerte Jobs, die RBAC-Rechte vergeben (ohne explizite Admin-Aktion).
|
||
- Unterstützung mehrerer paralleler RBAC-Profile pro Tenant (nur ein “recommended setup”).
|
||
|
||
---
|
||
|
||
## UX / Entry Points
|
||
|
||
### Entry Point: Tenant Detail (Filament)
|
||
|
||
Auf der `TenantResource` Detailseite im Action-Dropdown:
|
||
|
||
- `Setup Intune RBAC` (Wizard)
|
||
- `Admin consent`
|
||
- `Verify configuration`
|
||
|
||
**Visibility rules:**
|
||
- Nur für `status=active` Tenants.
|
||
- Nur wenn `app_client_id` gesetzt ist.
|
||
- Optional: Badge/Hint “RBAC missing” aus Health-Check.
|
||
|
||
**Copy/Help:**
|
||
- Kurze Erklärung: “Graph Permissions ≠ Intune RBAC”.
|
||
- Hinweis auf Least Privilege.
|
||
- Klarer Hinweis, dass Änderungen tenantweit wirken (je nach Scope).
|
||
|
||
---
|
||
|
||
## Wizard Flow
|
||
|
||
### Step 1 — Configuration (Role / Scope / Group)
|
||
|
||
**Inputs:**
|
||
- **Role** (Dropdown):
|
||
- Default: `Policy and Profile Manager` (Least Privilege für Policy/Config-Workflows)
|
||
- Optional: `Intune Administrator` (mit Warnung)
|
||
- **Scope** (Dropdown):
|
||
- Default: `Global / All devices` (wenn verfügbar)
|
||
- Optional: Auswahl einer Scope Group / Device Group (falls euer Modell das nutzt)
|
||
- **Group Mode**:
|
||
- Default: `Use Security Group (recommended)`
|
||
- Options:
|
||
- `Create new group` (Default-Name: `TenantPilot-Intune-RBAC`)
|
||
- `Use existing group` (Picker)
|
||
|
||
**UI Requirements:**
|
||
- “Review screen” zeigt *genau*, was erstellt/geändert wird (Role, Scope, Group).
|
||
|
||
### Step 2 — Delegated Admin Login
|
||
|
||
- Admin führt interaktiven Login durch (delegated).
|
||
- Wizard zeigt klar:
|
||
- welcher Tenant
|
||
- welche App (Client ID / Display Name, sofern auflösbar)
|
||
- dass nur kurzzeitig ein User-Token genutzt wird
|
||
|
||
**Security rule (mandatory):**
|
||
- Delegated Access Tokens werden **nicht persistiert** (keine Speicherung in DB/Cache).
|
||
- Tokens existieren nur im Request-Kontext / Session und werden nach Abschluss verworfen.
|
||
|
||
### Step 3 — Execute Setup (Idempotent + Safe)
|
||
|
||
Wizard führt folgenden Ablauf aus (alle Operationen tenant-scoped, über Graph-Abstraktion, mit Error-Mapping):
|
||
|
||
1) **Resolve Service Principal**
|
||
- Auflösen des Service Principals zur `tenant.app_client_id`.
|
||
- Wenn nicht gefunden:
|
||
- Wizard stoppt mit Hinweis: “Enterprise App ist im Tenant nicht vorhanden. Bitte zuerst Admin Consent durchführen.”
|
||
- Audit log: `tenant.rbac.setup.failed` (reason: sp_not_found)
|
||
|
||
2) **Ensure Security Group**
|
||
- Falls “Create new group”:
|
||
- Security Group erstellen (securityEnabled=true, mailEnabled=false).
|
||
- Wenn bereits vorhanden (gleiches displayName): wiederverwenden (oder per gespeicherter `rbac_group_id`).
|
||
- Falls “Use existing group”:
|
||
- Validieren: `securityEnabled=true`.
|
||
- Ergebnis-IDs werden gespeichert:
|
||
- `tenants.rbac_group_id` (neu, optional)
|
||
- `tenants.rbac_group_name` (optional, nur für UX)
|
||
|
||
3) **Ensure Membership (SP ∈ Group)**
|
||
- Service Principal als Member hinzufügen, wenn nicht vorhanden.
|
||
- Konflikte (already exists) müssen als OK behandelt werden.
|
||
|
||
4) **Ensure Intune Role Assignment**
|
||
- Suche nach existierendem Role Assignment, das:
|
||
- die gewünschte RoleDefinition referenziert
|
||
- die Group als Member enthält
|
||
- den gewünschten Scope abdeckt
|
||
- Wenn vorhanden: **Patch/Update** (z. B. Scope ergänzen)
|
||
- Wenn nicht vorhanden: **Create** Role Assignment
|
||
|
||
5) **Post-Verify (mandatory)**
|
||
- Direkt nach Setup:
|
||
- `Verify configuration` ausführen (inkl. Permission-Matrix Update)
|
||
- Zusätzlich 1–2 “Canary Calls” gegen Intune-Endpunkte, die für v1 kritisch sind (Read-Only reicht).
|
||
- Ergebnisse werden in Tenant-Health gespeichert (`app_status`, permissions health).
|
||
|
||
**Execution mode (v1):**
|
||
- Wizard führt die Steps synchron aus (kein Queue-Job), um Token-Probleme zu vermeiden.
|
||
- Timeouts: klare Fehlermeldung + Audit.
|
||
|
||
### Step 4 — Summary
|
||
|
||
- Wizard zeigt:
|
||
- Group (Name + ObjectId)
|
||
- Role (Name)
|
||
- Scope (global / group id)
|
||
- RoleAssignmentId (falls verfügbar)
|
||
- Verify result (OK / Partial / Failed)
|
||
- CTA: “Retry policy sync”
|
||
|
||
---
|
||
|
||
## Data Model Additions (minimal)
|
||
|
||
Erweiterung `tenants` (optional aber empfohlen, für Transparenz & Idempotenz):
|
||
|
||
- `rbac_group_id` (nullable string/GUID)
|
||
- `rbac_role_assignment_id` (nullable string/GUID)
|
||
- `rbac_role_key` (nullable string; z. B. `policy_profile_manager`)
|
||
- `rbac_scope_mode` (nullable string; z. B. `global|group`)
|
||
- `rbac_scope_id` (nullable string/GUID)
|
||
|
||
> Hinweis: Wenn ihr strikt ohne zusätzliche Felder arbeiten wollt, geht es auch rein über Discovery,
|
||
> aber gespeicherte IDs machen den Wizard deutlich stabiler und schneller.
|
||
|
||
---
|
||
|
||
## Functional Requirements (additions)
|
||
|
||
- **FR-023**: System MUST expose a per-tenant onboarding wizard “Setup Intune RBAC” in Filament.
|
||
- **FR-024**: Wizard MUST use delegated admin login and MUST NOT store delegated tokens.
|
||
- **FR-025**: Wizard MUST be idempotent (re-run safe) and MUST converge to the desired RBAC state.
|
||
- **FR-026**: Wizard MUST support group-based RBAC membership (recommended) and MUST ensure the service principal is a member.
|
||
- **FR-027**: Wizard MUST create or update Intune role assignments with an explicit role + scope.
|
||
- **FR-028**: Wizard MUST run a post-setup verification that updates tenant health and permissions UI.
|
||
- **FR-029**: Wizard MUST write audit logs for start, each step outcome, and final result (success/failed/partial).
|
||
- **FR-030**: Wizard MUST enforce tenant isolation and use explicit tenant context (no implicit defaults).
|
||
|
||
---
|
||
|
||
## Non-Functional / Safety Requirements
|
||
|
||
- Least Privilege:
|
||
- Default role selection is non-global admin (Policy/Profile manager).
|
||
- Selecting higher-privilege roles shows a warning and requires explicit confirmation.
|
||
- Clear Failure UX:
|
||
- Every failure must map to an actionable message (e.g., “Admin consent missing”, “Insufficient directory permissions”).
|
||
- No secrets:
|
||
- No access tokens, secrets, or payloads in logs/audits.
|
||
- Deterministic logging:
|
||
- Audit entries include tenant_id, actor_user_id, action_key, resource IDs (group, roleAssignment), and status.
|
||
|
||
---
|
||
|
||
## Acceptance Scenarios
|
||
|
||
1) **Missing RBAC → Wizard fixes it**
|
||
- **Given** ein aktiver Tenant mit konfigurierter App (`app_client_id`) und Admin Consent,
|
||
aber Intune Calls liefern RBAC-403,
|
||
- **When** der Admin den Wizard ausführt,
|
||
- **Then** wird Gruppe+Membership+RoleAssignment hergestellt, Verify wird OK, und Policy Sync funktioniert.
|
||
|
||
2) **Admin Consent fehlt**
|
||
- **Given** `app_client_id` ist gesetzt, aber der Service Principal kann nicht aufgelöst werden,
|
||
- **When** Wizard startet,
|
||
- **Then** bricht er mit “Bitte zuerst Admin Consent durchführen” ab und schreibt Audit `sp_not_found`.
|
||
|
||
3) **Idempotenz**
|
||
- **Given** Wizard wurde bereits erfolgreich ausgeführt,
|
||
- **When** Wizard erneut mit gleichen Einstellungen ausgeführt wird,
|
||
- **Then** werden keine Duplikate erzeugt, und die Summary zeigt “No changes / Already compliant”.
|
||
|
||
4) **Insufficient privileges**
|
||
- **Given** ein Admin loggt sich ein, aber hat nicht die nötigen Rechte,
|
||
- **When** Setup ausgeführt wird,
|
||
- **Then** stoppt der Wizard mit klarer Fehlermeldung pro Step (z. B. group create / role assignment create),
|
||
und Audit enthält den Step und Fehlercode.
|
||
|
||
5) **Restricted scope**
|
||
- **Given** Admin wählt eine eingeschränkte Scope Group,
|
||
- **When** Setup abgeschlossen ist,
|
||
- **Then** Verify markiert ggf. “Partial” mit Hinweis “Inventory limited by scope”.
|
||
|
||
---
|
||
|
||
## Implementation Notes (for plan.md linkage)
|
||
|
||
- Reuse existing services pattern:
|
||
- `IntuneRbacSetupService` (new) in `app/Services/Intune/`
|
||
- Uses `GraphClientInterface` and existing error mapping/logging hooks.
|
||
- Uses `AuditLogger` for stepwise audit events.
|
||
- Extend `TenantPermissionService` (User Story 7 in tasks) to include an RBAC check state:
|
||
- status: `ok|missing|error`
|
||
- message: “Intune RBAC role assignment missing (Wizard required)”
|
||
- Add Filament wizard page/action under `TenantResource`.
|
||
|
||
---
|
||
|
||
## Edge Cases
|
||
|
||
- Group exists but is not security-enabled → fail with actionable message.
|
||
- Role assignment exists but wrong scope → patch and warn.
|
||
- Multiple “similar” groups by name → prefer stored `rbac_group_id` if present, else prompt.
|
||
- Tenant mismatch: Wizard must never operate on non-selected tenant (enforce `Tenant::current()` or explicit tenant param).
|
||
- Token expiry mid-run → show “Please retry” + audit partial.
|
||
|
||
|
||
Previous draft archived under spechistory/spec.md |