TenantAtlas/specs/062-tenant-rbac-v1/spec.md
Ahmed Darrazi 3b1dd98f52 feat(rbac): Implement Tenant RBAC v1
This commit introduces a comprehensive Role-Based Access Control (RBAC) system for TenantAtlas.

- Implements authentication via Microsoft Entra ID (OIDC).
- Manages authorization on a per-Suite-Tenant basis using a  table.
- Follows a capabilities-first approach, using Gates and Policies.
- Includes a break-glass mechanism for platform superadmins.
- Adds policies for bootstrapping tenants and managing admin responsibilities.
2026-01-25 16:01:50 +01:00

84 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Feature Specification: Tenant RBAC v1 (Entra Login + Suite-Tenant Memberships)
**Feature Branch**: `062-tenant-rbac-v1`
**Created**: 2026-01-25
**Status**: Draft
**Input**: User description: "# Feature 062 — Tenant RBAC v1 (Entra Login + Suite-Tenant Memberships + Optional Group/AppRole Mapping + Break-glass) **Stack:** Laravel 12 · PHP 8.4 · Filament v5 · Livewire v4 · Postgres **Scope:** Best-practice, scalable authorization for a multi-tenant SaaS/MSP suite: - Authentication via Microsoft Entra ID (OIDC) - Authorization via **suite-tenant** memberships in TenantAtlas (SoT) - Optional automation: Entra Groups / App Roles → memberships - Platform break-glass local superadmin (operator-safe, audited) **Goal:** Enable MSPs and customers to manage who can do what per Suite Tenant (customer + env), without mixing Microsoft tenant admin concepts into TenantAtlas authorization. --- ## 0. Key Concepts (avoid confusion) ### K-001 — Two different “tenants” 1) **Suite Tenant (TenantAtlas Tenant)**: a customer/environment container inside your app (e.g., “Customer A PROD”, “Customer A DEV”). 2) **Microsoft Tenant (Entra/Intune tenant)**: the target platform you connect to (Entra tenant ID GUID). TenantAtlas RBAC controls who can use TenantAtlas features per **Suite Tenant**. Microsoft (Entra/Intune) RBAC controls what the app is technically able to read/write. --- ## 1. Principles (Constitution-aligned) ### RBAC-001 — Capabilities-first, not role checks Feature code MUST check **capabilities/permissions** (Gates/Policies), never `if role == X`. Roles are a mapping layer only. ### RBAC-002 — Suite Tenant is the authorization scope All permissions are evaluated within the current Suite Tenant context. ### RBAC-003 — Audited changes All membership/role/permission changes must write an AuditLog entry (no secrets, no PII dumps). ### RBAC-004 — Safe defaults If a user is not a member of a Suite Tenant: deny access (404/403), no “implicit member”. ### RBAC-005 — Break-glass exists A local platform superadmin account exists to recover access when Entra integration fails. --- ## 2. Goals 1) Allow adding/removing members per Suite Tenant and setting their role via UI. 2) Support MSP reality: same person can have different roles across different Suite Tenants. 3) Keep authorization stable even if Entra groups change (source recorded). 4) Allow optional Entra group/app-role mapping to auto-provision memberships. 5) Support future fine-grained permissions without refactoring all features. --- ## 3. Non-Goals (v1) - Full local user management for all users (passwords etc.) — only break-glass. - Cross-tenant global RBAC (platform-wide “auditor across all tenants”) beyond superadmin. - Impersonation (future). - Per-resource row-level permissions beyond tenant scope (future). - Full SCIM provisioning (future). --- # 4. Role Model & Capabilities ## 4.1 Canonical roles (v1) - `owner` - `manager` - `operator` - `readonly` ## 4.2 Canonical capabilities (v1 baseline) Define these high-level permissions (more can be added later): **General** - `tenant.view` - `tenant.manage` (edit tenant metadata) **Providers (from Feature 061)** - `provider.view` - `provider.manage` (connections + credentials) - `provider.run` (check/snapshot/sync) **Operations** - `ops.view` (Monitoring → Operations) - `ops.run` (start ops) **Inventory** - `inventory.view` - `inventory.run` (run sync) **Policies** - `policy.view` - `policy.run` (sync, bulk ops) - `policy.restore` (if applicable) **Backups/Restore** - `backup.view` - `backup.run` - `restore.view` - `restore.execute` (high-risk) **Drift/Findings** - `drift.view` - `drift.run` ## 4.3 Role → capability mapping (v1 defaults) **Owner** - all capabilities for the Suite Tenant **Manager** - all except the highest-risk destructive actions (if you want a stricter split) - recommended v1: same as owner except `restore.execute` (optional) **Operator** - view + run operational tasks - cannot manage providers/credentials or tenant config - cannot execute destructive ops unless explicitly granted later **Readonly** - view-only across all tenant features ### Minimum enforced mapping for Provider v1 (must match Feature 061) - Owner/Manager: `provider.view`, `provider.manage`, `provider.run` - Operator: `provider.view`, `provider.run` - Readonly: `provider.view` --- # 5. Data Model ## 5.1 users Store Entra identity as stable keys: - `entra_tenant_id` (tid) - `entra_object_id` (oid) - `email` (optional/nullable; do not rely on it as identity) - `name` - timestamps Unique index: - `(entra_tenant_id, entra_object_id)`. ## 5.2 tenant_memberships (Suite Tenant authorization SoT) - `id` (uuid) - `tenant_id` (fk to suite tenant) - `user_id` (fk) - `role` (enum string) - `source` (`manual | entra_group | entra_app_role | break_glass`) - `source_ref` (nullable string; e.g., group id/app role id) - `created_by_user_id` (nullable; for manual changes) - `timestamps` Constraints: - unique `(tenant_id, user_id)` - index `(tenant_id, role)` ## 5.3 tenant_role_mappings (optional automation) Allows automatic provisioning based on Entra groups/app roles. - `id` (uuid) - `tenant_id` (fk) - `mapping_type` (`entra_group | entra_app_role`) - `external_id` (group GUID or appRole string) - `role` (enum string) - `is_enabled` (bool) - timestamps Constraints: - unique `(tenant_id, mapping_type, external_id)` ## 5.4 audit_logs (existing) Used for membership changes; must support: - actor - tenant - action_id - target - before/after (redacted) - timestamp --- # 6. Authentication & Provisioning ## 6.1 Entra login (OIDC) On successful login: 1) Upsert `users` by `(entra_tenant_id, entra_object_id)` 2) Determine accessible Suite Tenants by memberships 3) Set current tenant context via Filament tenant selection ## 6.2 Membership sources and precedence (v1) Membership evaluation per Suite Tenant: 1) Manual membership (`source=manual`) takes precedence over automated sources. 2) If no manual membership exists: - apply Entra mapping rules (groups/app roles) if configured. 3) If still no membership: deny. Record the source: - If created by mapping: `source=entra_group/app_role`, `source_ref=<id>` ## 6.3 Entra group overage handling (v2-ready) v1 may rely on claims if available; if group claims are not present: - v1: treat mapping as optional; show “group overage not supported yet” - v2: add Graph call to resolve group membership (OperationRun-backed admin job, not render-time) --- # 7. Authorization (Policies/Gates) ## 7.1 Capability engine Implement a central `CapabilityResolver`: - input: user, tenant, membership role, optional overrides - output: `can($capability)` boolean All feature policies call `can()`. Forbidden: - direct role comparisons in feature code ## 7.2 Tenant scoping enforcement Every Filament resource query MUST scope to the current Suite Tenant and MUST enforce membership: - Non-members see 404 (deny-as-not-found) on tenant resources. --- # 8. UI Requirements (Filament) ## 8.1 Tenant “Members” management Add to **Settings → Tenants**: - Tab/Relation Manager: **Members** - Search existing users (by email/name) - Add member (select user) + role - Edit role - Remove member Permissions: - only Owner/Manager with `tenant.manage` can manage members - Operator/Readonly can view member list only if `tenant.view` (optional) Every change writes AuditLog. ## 8.2 Role mapping configuration (optional) Tenant detail page: - “Role Mappings” section - Add mapping: group/app-role → role - enable/disable mapping - show last sync attempt (if you add later) v1 can ship with UI but without group-membership resolution (if you choose). ## 8.3 Break-glass admin UX When logged in as break-glass: - Show a persistent banner “Break-glass account” - All actions are audited with source `break_glass` --- # 9. Break-glass Local Superadmin (Platform) ## 9.1 Purpose Allow platform operator to recover access if Entra login/mapping breaks. ## 9.2 Minimal implementation (v1) - local user table entry flagged `is_platform_superadmin = true` - password-based auth (local) OR separate guard - platform superadmin can: - view/manage all Suite Tenants - manage memberships - manage provider connections - must be audited and clearly indicated Constraints: - only one/few accounts - not used for day-to-day operations --- # 10. Tests & Guardrails ## 10.1 RBAC tests (required) - Membership required: non-member cannot access tenant resources (deny-as-not-found) - Role mapping: - Owner/Manager can manage members - Operator cannot manage credentials (ties into Feature 061) - Readonly cannot start operations ## 10.2 Capability resolver tests - role → capability mapping is stable - adding a new capability wont break existing roles (safe defaults) ## 10.3 Audit tests - membership changes create audit log records - no secrets/PII in audit before/after ## 10.4 UI tests (Filament) - Members relation manager visible only to authorized roles - Role dropdown reflects canonical roles --- # 11. Migration & Backward Compatibility - Introduce membership enforcement gradually: - v1: create a default owner membership for the creator of a tenant (migration/seed) - existing dev tenants: backfill membership for current admin user - Ensure no tenant becomes inaccessible after deployment: - platform superadmin can always recover --- # 12. Task Plan (high level) ## Phase 0 — Discovery - Identify existing “tenant scoping” and any implicit access assumptions. - Locate current auth flow and where to hook Entra identity upsert. ## Phase 1 — Schema - `tenant_memberships` - `tenant_role_mappings` (optional) - user identity fields (`entra_tenant_id`, `entra_object_id`) if not present ## Phase 2 — Capability resolver + gates - Implement `CapabilityResolver` - Register Gates: `provider.manage`, `provider.run`, etc. ## Phase 3 — UI - Tenant Members manager (CRUD) - Optional role mapping UI ## Phase 4 — Break-glass - platform_superadmin account + banner + audit ## Phase 5 — Tests - RBAC + audit + UI tests --- # 13. Acceptance Criteria (DoD) 1) Users authenticate via Entra; user identities are stored by stable `(tid, oid)`. 2) A users access is determined by tenant_memberships for the current Suite Tenant. 3) Owner/Manager can add/remove members and set roles via UI; changes are audited. 4) Operator/Readonly restrictions are enforced across provider management and operations. 5) Optional: Entra group/app-role mappings can auto-provision memberships (at least config + data model). 6) Break-glass platform superadmin exists and can recover access; actions are audited and bannered. 7) No feature code uses direct role checks; only capabilities/gates/policies.
---
## Addendum — Bootstrap & Admin Responsibilities
### Context
TenantAtlas operates across two distinct administrative planes:
- **Microsoft tenant administration (Entra/Intune)** governs what the platform is technically allowed to do in the target Microsoft tenant (consent, permissions, RBAC assignments).
- **TenantAtlas administration (Suite Tenant RBAC)** governs who is allowed to use TenantAtlas features for a given Suite Tenant (memberships, roles, operational actions).
These roles may be held by the same person in small orgs, but must not be assumed to be the same in enterprise/MSP environments.
### FR-013 Admin Planes Are Distinct (No Assumptions)
The system MUST NOT assume that:
- a Microsoft tenant admin is automatically a TenantAtlas admin, or
- a TenantAtlas admin is automatically a Microsoft tenant admin.
TenantAtlas permissions are determined solely by `tenant_memberships` (and optional mappings), independent of Microsoft directory roles.
**Acceptance:**
- A user can be a TenantAtlas Owner/Manager without holding any Microsoft admin role.
- A Microsoft admin can grant consent without being a TenantAtlas member (unless explicitly added).
### FR-014 Bootstrap Owner Assignment (First-Admin Rule)
Each Suite Tenant MUST always have at least one Owner-capable administrator to prevent lockout.
#### FR-014a Default Bootstrap Rule (v1)
On Suite Tenant creation, the creator is assigned as `owner` membership (source: `manual`, created_by = creator).
#### FR-014b Platform Recovery Rule (Break-glass)
A platform superadmin MUST be able to assign/change the initial owner membership in case:
- the creator no longer exists,
- Entra login/mapping breaks,
- the tenant was imported.
#### FR-014c Non-Removable Last Owner Guard
The system MUST prevent removing or demoting the last remaining `owner` membership for a Suite Tenant.
**Acceptance:**
- Attempting to remove the last Owner fails with a clear message.
- Platform superadmin can recover by adding a new Owner first.
### FR-015 TenantAtlas Admin Responsibilities (v1)
TenantAtlas admins (Owner/Manager) are responsible for:
- managing Suite Tenant members and roles (tenant_memberships),
- approving who can run operations and manage providers inside TenantAtlas,
- reviewing audit logs for membership/role changes.
Microsoft tenant admins are responsible for:
- granting admin consent for the app,
- ensuring required Graph permissions are granted,
- maintaining any Microsoft-side RBAC/group prerequisites.
**Acceptance:**
- The UI communicates this split (e.g., small help text near membership management and provider setup links).
### FR-016 Audit Requirements for Bootstrap & Membership Management
All bootstrap and membership management actions MUST be audited with canonical action_ids:
- `tenant_membership.add`
- `tenant_membership.role_change`
- `tenant_membership.remove`
- `tenant_membership.bootstrap_assign` (initial owner assignment)
- `tenant_membership.bootstrap_recover` (platform superadmin recovery)
Audit entries MUST include:
- actor id
- suite tenant id
- target user id/email (minimal)
- before/after role (redacted)
- timestamp
No secrets, no tokens, no Microsoft credentials.
### UI Requirements (Bootstrap-related)
- Tenant creation flow MUST result in an Owner membership being present (visible in Tenant → Members).
- Tenant → Members UI MUST show an “Owner” role and enforce “last owner cannot be removed/demoted”.
- Platform superadmin UI MUST support adding an Owner membership if none exists or recovery is required.