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

14 KiB
Raw Blame History

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.

  • 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.