spec: 105 Entra Admin Roles Evidence + Findings (spec + plan + checklist)
This commit is contained in:
parent
ef380b67d1
commit
dbb7f1fbab
@ -0,0 +1,46 @@
|
||||
# Specification Quality Checklist: Entra Admin Roles Evidence + Findings
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-02-21
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs) in user stories
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders (user stories section)
|
||||
- [x] All mandatory sections completed (Scope Fields, User Scenarios, Requirements, Success Criteria, UI Action Matrix)
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous (21 FRs, each with MUST + verifiable condition)
|
||||
- [x] Success criteria are measurable (SC-001 through SC-008 with quantitative metrics)
|
||||
- [x] Success criteria are technology-agnostic (no framework/language references)
|
||||
- [x] All acceptance scenarios are defined (6 user stories with 18 total acceptance scenarios)
|
||||
- [x] Edge cases are identified (7 documented: partial data, service principals, scoped assignments, missing template_id, zero assignments, concurrent scans, threshold hardcode)
|
||||
- [x] Scope is clearly bounded (Non-Goals section: no PIM, no remediation, no EvidenceItems, no RBAC refactor)
|
||||
- [x] Dependencies and assumptions identified (Spec 104, Spec 099, Findings model, Graph RBAC API, no PIM)
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria (FRs map to acceptance scenarios in user stories)
|
||||
- [x] User scenarios cover primary flows (scan → report → findings → alerts → UI)
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification (entities section describes domain concepts, not code)
|
||||
|
||||
## Constitution Alignment
|
||||
|
||||
- [x] Constitution alignment (required) — Graph contracts, safety gates, tenant isolation, run observability, tests
|
||||
- [x] Constitution alignment (RBAC-UX) — authorization planes, 404/403 semantics, capability registry, authorization tests
|
||||
- [x] Constitution alignment (OPS-EX-AUTH-001) — N/A documented
|
||||
- [x] Constitution alignment (BADGE-001) — new finding type badge documented
|
||||
- [x] Constitution alignment (Filament Action Surfaces) — UI Action Matrix completed
|
||||
- [x] Constitution alignment (UX-001) — Exemption for no new Create/Edit pages documented
|
||||
|
||||
## Notes
|
||||
|
||||
- All items pass. Spec is ready for `/speckit.plan` or implementation.
|
||||
- Plan.md has been written alongside the spec.
|
||||
- High-Privilege Role Catalog includes Microsoft well-known template IDs for v1 classification.
|
||||
- "Too many Global Admins" threshold is hardcoded at 5 with documented TODO for settings migration.
|
||||
195
specs/105-entra-admin-roles-evidence-findings/plan.md
Normal file
195
specs/105-entra-admin-roles-evidence-findings/plan.md
Normal file
@ -0,0 +1,195 @@
|
||||
# Plan — 105 Entra Admin Roles Evidence + Findings
|
||||
|
||||
**Feature Branch**: `105-entra-admin-roles-evidence-findings`
|
||||
**Created**: 2026-02-21
|
||||
**Depends on**: Spec 104 (stored_reports, posture patterns), Alerts v1 (099), Findings + OperationRuns
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Infrastructure & Permissions Registry
|
||||
|
||||
### 1.1 Add Entra required permissions registry
|
||||
|
||||
- Create `config/entra_permissions.php` following `config/intune_permissions.php` schema.
|
||||
- Declare `RoleManagement.Read.Directory` (type: application, features: `['entra-admin-roles']`).
|
||||
- Optionally: `Directory.Read.All` as fallback entry (type: application, features: `['entra-admin-roles']`).
|
||||
|
||||
### 1.2 Merge registry loader
|
||||
|
||||
- Locate where `config('intune_permissions.permissions')` is loaded (TenantPermissionService or equivalent).
|
||||
- Extend the loader to also read `config('entra_permissions.permissions')` and merge into the combined required-permissions list.
|
||||
- Existing Intune posture flows must remain unchanged (no breaking change).
|
||||
- Add test: registry merger returns combined set; existing tests still pass.
|
||||
|
||||
### 1.3 Register Graph endpoints in `config/graph_contracts.php`
|
||||
|
||||
- Add entries for:
|
||||
- `GET /roleManagement/directory/roleDefinitions`
|
||||
- `GET /roleManagement/directory/roleAssignments?$expand=principal`
|
||||
- Follow existing contract registration patterns (endpoint key, method, URI, scopes).
|
||||
|
||||
### 1.4 Add RBAC capabilities
|
||||
|
||||
- Add to `App\Support\Auth\Capabilities`:
|
||||
- `ENTRA_ROLES_VIEW = 'entra_roles.view'`
|
||||
- `ENTRA_ROLES_MANAGE = 'entra_roles.manage'`
|
||||
- Update role-to-capability mapping:
|
||||
- Readonly/Operator → `ENTRA_ROLES_VIEW`
|
||||
- Manager/Owner → `ENTRA_ROLES_VIEW` + `ENTRA_ROLES_MANAGE`
|
||||
|
||||
### 1.5 Add OperationRunType case
|
||||
|
||||
- Add `EntraAdminRolesScan = 'entra.admin_roles.scan'` to `App\Support\OperationRunType` enum.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Core Services
|
||||
|
||||
### 2.1 Implement `HighPrivilegeRoleCatalog`
|
||||
|
||||
- Create `App\Services\EntraAdminRoles\HighPrivilegeRoleCatalog`.
|
||||
- Hardcoded mapping of template_id → (display_name, severity).
|
||||
- Public methods:
|
||||
- `isHighPrivilege(string $templateId, ?string $displayName): bool`
|
||||
- `severityFor(string $templateId, ?string $displayName): ?string`
|
||||
- `allTemplateIds(): array`
|
||||
- `globalAdminTemplateId(): string`
|
||||
- Fallback: if `template_id` not in catalog, check `display_name`.
|
||||
- Full unit test coverage.
|
||||
|
||||
### 2.2 Implement `EntraAdminRolesReportService`
|
||||
|
||||
- Create `App\Services\EntraAdminRoles\EntraAdminRolesReportService`.
|
||||
- Responsibilities:
|
||||
1. Fetch `roleDefinitions` via Graph (using registered contract).
|
||||
2. Fetch `roleAssignments?$expand=principal` via Graph.
|
||||
3. Build payload per spec (role_definitions, role_assignments, totals, high_privilege).
|
||||
4. Compute fingerprint: SHA-256 of sorted `(role_template_or_id, principal_id, scope_id)` tuples.
|
||||
5. Determine `previous_fingerprint` from latest existing report for this tenant.
|
||||
6. Dedupe: if current fingerprint == latest report's fingerprint, skip creation.
|
||||
7. Create `StoredReport` with `report_type=entra.admin_roles`.
|
||||
- Add `REPORT_TYPE_ENTRA_ADMIN_ROLES = 'entra.admin_roles'` constant to `StoredReport` model.
|
||||
- Tests: report creation, dedupe, fingerprint computation, previous_fingerprint chaining.
|
||||
|
||||
### 2.3 Implement `EntraAdminRolesFindingGenerator`
|
||||
|
||||
- Create `App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator`.
|
||||
- Responsibilities:
|
||||
1. Accept report payload (or structured DTO).
|
||||
2. For each role assignment where role is high-privilege (per `HighPrivilegeRoleCatalog`):
|
||||
- Compute fingerprint: `entra_admin_role:{tenant_id}:{role_template_or_id}:{principal_id}:{scope_id}`
|
||||
- Upsert finding: `finding_type=entra_admin_roles`, `source=entra.admin_roles`
|
||||
- Set severity per catalog (critical for GA, high otherwise)
|
||||
- Populate `evidence_jsonb` with role, principal, scope details
|
||||
- Update `times_seen`, `last_seen_at` on existing findings
|
||||
3. Aggregate check: count GA principals > threshold (5) → aggregate finding with distinct fingerprint.
|
||||
4. Auto-resolve: query open findings for this tenant+source not in current scan's fingerprint set → resolve.
|
||||
5. Re-open: if a resolved finding's fingerprint matches current data → set status=new, clear resolved fields.
|
||||
6. Collect alert events for new/re-opened findings.
|
||||
- Add `FINDING_TYPE_ENTRA_ADMIN_ROLES = 'entra_admin_roles'` constant to `Finding` model.
|
||||
- Tests: creation, idempotency, auto-resolve, re-open, aggregate finding, alert events.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Job & Scheduling
|
||||
|
||||
### 3.1 Implement `ScanEntraAdminRolesJob`
|
||||
|
||||
- Create `App\Jobs\EntraAdminRoles\ScanEntraAdminRolesJob` (implements `ShouldQueue`).
|
||||
- Lifecycle:
|
||||
1. Check tenant has provider connection; skip if not.
|
||||
2. Create OperationRun (`entra.admin_roles.scan`, status=running).
|
||||
3. Call `EntraAdminRolesReportService::generate()`.
|
||||
4. Call `EntraAdminRolesFindingGenerator::generate()`.
|
||||
5. Dispatch alert events via `AlertDispatchService`.
|
||||
6. Mark OperationRun completed (success/failure with error details).
|
||||
- Active-run uniqueness: check for existing running OperationRun before starting.
|
||||
- Tests: job dispatching, OperationRun lifecycle, skip on no connection, failure handling.
|
||||
|
||||
### 3.2 Add to workspace dispatcher
|
||||
|
||||
- Register `ScanEntraAdminRolesJob` in the daily workspace dispatch schedule.
|
||||
- Same pattern as existing scheduled scans: iterate tenants with active connections, dispatch per tenant.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Alerts Integration
|
||||
|
||||
### 4.1 Add alert event type
|
||||
|
||||
- Add constant `EVENT_ENTRA_ADMIN_ROLES_HIGH = 'entra.admin_roles.high'` to `AlertRule` model.
|
||||
- Add to event type options array (used in alert rule form dropdown).
|
||||
|
||||
### 4.2 Extend `EvaluateAlertsJob`
|
||||
|
||||
- Add a new producer section in `EvaluateAlertsJob` that:
|
||||
- Queries findings where `source=entra.admin_roles`, `severity>=high`, `status` in (`new`).
|
||||
- Produces events with `event_type=entra.admin_roles.high`.
|
||||
- Uses finding fingerprint as `fingerprint_key` for cooldown/dedupe.
|
||||
- Tests: alert rule matching, delivery creation, cooldown.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Badge Catalog
|
||||
|
||||
### 5.1 Add `entra_admin_roles` to `FindingTypeBadge`
|
||||
|
||||
- Extend `App\Support\Badges\Domains\FindingTypeBadge` mapper with:
|
||||
- `Finding::FINDING_TYPE_ENTRA_ADMIN_ROLES => new BadgeSpec('Entra admin roles', 'danger', 'heroicon-m-shield-exclamation')`
|
||||
- Test: badge renders correctly for new type.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — UI
|
||||
|
||||
### 6.1 Tenant card widget "Admin Roles"
|
||||
|
||||
- Create a Livewire/Filament widget for the tenant dashboard.
|
||||
- Displays: last scan timestamp, high-privilege assignment count.
|
||||
- CTA: "Scan now" (visible only if user has `ENTRA_ROLES_MANAGE`).
|
||||
- Link: "View latest report" (stored reports viewer filtered by `entra.admin_roles`).
|
||||
- Empty state: "No scan performed" + gated "Scan now" CTA.
|
||||
|
||||
### 6.2 Report viewer enhancements
|
||||
|
||||
- Extend existing stored reports viewer to handle `report_type=entra.admin_roles`:
|
||||
- Summary section showing totals.
|
||||
- Table of high-privilege role assignments (principal display name, type, role, scope).
|
||||
- Filter by `report_type` on report listing.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — Tests
|
||||
|
||||
### 7.1 Unit tests
|
||||
|
||||
- `HighPrivilegeRoleCatalog`: classification by template_id, fallback to display_name, unknown role returns false.
|
||||
- `EntraAdminRolesReportService`: report creation, fingerprint computation, dedupe, previous_fingerprint.
|
||||
- `EntraAdminRolesFindingGenerator`: finding creation, severity assignment, idempotent upsert, auto-resolve, re-open, aggregate finding.
|
||||
- Registry merge: combined Intune + Entra permissions.
|
||||
|
||||
### 7.2 Feature tests
|
||||
|
||||
- `ScanEntraAdminRolesJob`: full lifecycle (report + findings + alerts), skip unconnected, failure handling.
|
||||
- Alerts: event type matching, delivery creation, cooldown.
|
||||
- RBAC: `ENTRA_ROLES_VIEW` can view; `ENTRA_ROLES_MANAGE` can scan; missing capability → 403; non-member → 404.
|
||||
|
||||
### 7.3 Posture integration smoke test
|
||||
|
||||
- With Entra permissions in merged registry, posture generator includes them in score calculation.
|
||||
|
||||
### 7.4 Run full suite
|
||||
|
||||
- `vendor/bin/sail artisan test --compact` — all tests green.
|
||||
- `vendor/bin/sail bin pint --dirty` — code style clean.
|
||||
|
||||
---
|
||||
|
||||
## Filament v5 Compliance Notes
|
||||
|
||||
1. **Livewire v4.0+**: All widget components are Livewire v4 compatible.
|
||||
2. **Provider registration**: No new panel providers — existing admin panel in `bootstrap/providers.php`.
|
||||
3. **Global search**: No new globally searchable resources.
|
||||
4. **Destructive actions**: "Scan now" is non-destructive (read-only Graph call); no `requiresConfirmation()` needed.
|
||||
5. **Asset strategy**: No new heavy assets. Widget uses standard Filament components.
|
||||
6. **Testing plan**: Widget tested as Livewire component; finding generator + report service unit tested; job feature tested; RBAC positive/negative tests included.
|
||||
346
specs/105-entra-admin-roles-evidence-findings/spec.md
Normal file
346
specs/105-entra-admin-roles-evidence-findings/spec.md
Normal file
@ -0,0 +1,346 @@
|
||||
# Feature Specification: Entra Admin Roles Evidence + Findings
|
||||
|
||||
**Feature Branch**: `105-entra-admin-roles-evidence-findings`
|
||||
**Created**: 2026-02-21
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Entra Admin Roles Evidence + Findings (StoredReports + Alerts) — auditierbar beantworten: wer hat welche Entra Admin-Rollen, welche sind high privilege, hat sich die Lage verändert (drift), und kann ich das als Evidence snapshotten und als Findings/Alerts operationalisieren."
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: tenant (per-tenant Entra role assignment posture) + workspace (alerts infrastructure extends workspace-level)
|
||||
- **Primary Routes**:
|
||||
- No new standalone Filament pages in this spec
|
||||
- Existing: Monitoring > Findings (extended with `finding_type=entra_admin_roles`)
|
||||
- Existing: Monitoring > Alerts (extended with new `entra.admin_roles.high` event type)
|
||||
- Existing: Tenant dashboard card showing admin roles summary (new card widget)
|
||||
- Existing: Stored Reports viewer filtered to `report_type=entra.admin_roles`
|
||||
- **Data Ownership**:
|
||||
- `stored_reports` — tenant-owned, reused from Spec 104 (new `report_type=entra.admin_roles`)
|
||||
- `findings` — tenant-owned, extended with `finding_type=entra_admin_roles` (existing table)
|
||||
- `config/entra_permissions.php` — new registry file for Entra-specific Graph permissions (workspace infra)
|
||||
- **RBAC**:
|
||||
- Workspace membership required for any access (non-members → 404)
|
||||
- New capabilities: `ENTRA_ROLES_VIEW` (see report, see findings), `ENTRA_ROLES_MANAGE` (trigger on-demand scan)
|
||||
- Readonly/Operator roles → `ENTRA_ROLES_VIEW`
|
||||
- Manager/Owner roles → `ENTRA_ROLES_MANAGE`
|
||||
- Findings remain gated by existing `TENANT_FINDINGS_ACKNOWLEDGE` for acknowledge actions
|
||||
|
||||
## Assumptions and Dependencies
|
||||
|
||||
- **Spec 104 (Provider Permission Posture)** established the `stored_reports` table, `StoredReport` model, fingerprint-based dedupe, and the posture report pattern. This spec reuses that infrastructure with a new `report_type`.
|
||||
- **Alerts v1 (Spec 099)** provides the generic `AlertDispatchService`, `EvaluateAlertsJob` framework, and `AlertRule` model. This spec adds a new event type constant; no structural changes to alerting.
|
||||
- **Findings model** already supports `finding_type`, `source`, `subject_type`, `subject_external_id`, severity, status (including `resolved`), and fingerprint-based idempotent upsert (established by drift findings + Spec 104).
|
||||
- **`config/intune_permissions.php`** is the existing registry for Intune-specific Graph permissions. A new `config/entra_permissions.php` will be created for Entra Directory permissions. The registry loader (`TenantPermissionService` or equivalent) must merge both sources.
|
||||
- **Microsoft Graph unified RBAC API** provides `roleManagement/directory/roleDefinitions` and `roleManagement/directory/roleAssignments?$expand=principal` for reading directory role data.
|
||||
- **No PIM/Eligible Assignments** in v1 — only active (permanent) role assignments are covered.
|
||||
- **No automatic remediation** — this spec covers evidence collection, findings, and alerts only.
|
||||
|
||||
## User Scenarios and Testing *(mandatory)*
|
||||
|
||||
### User Story 1 — Scan Entra admin role assignments and produce evidence snapshot (Priority: P1)
|
||||
|
||||
As a workspace operator, I want the system to scan a tenant's Entra directory role definitions and active role assignments, then persist the results as a stored report, so that I have an auditable snapshot of who holds which admin roles at any point in time.
|
||||
|
||||
**Why this priority**: This is the foundational data pipeline. Without fetching and persisting role assignment data, no findings, alerts, or UI can function.
|
||||
|
||||
**Independent Test**: Trigger an on-demand scan for a tenant; confirm a `stored_report` record exists with `report_type=entra.admin_roles`, a valid payload containing `role_definitions`, `role_assignments`, `totals`, and a content-based fingerprint.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has a configured provider connection with `RoleManagement.Read.Directory` permission, **When** the admin roles scan runs, **Then** a `stored_report` is created with `report_type=entra.admin_roles`, `tenant_id` set, and a JSONB payload containing `role_definitions`, `role_assignments`, and `totals`.
|
||||
2. **Given** the scan runs twice with identical role assignment data, **When** the second scan completes, **Then** no duplicate stored report is created (fingerprint-based dedupe).
|
||||
3. **Given** a role assignment has changed between scans (e.g., new Global Admin added), **When** the second scan completes, **Then** a new stored report is created with a different fingerprint, and `previous_fingerprint` references the prior report's fingerprint.
|
||||
4. **Given** the tenant has no provider connection, **When** the scan is dispatched, **Then** the job completes gracefully with no stored report or findings created.
|
||||
5. **Given** the Graph API is unreachable during the scan, **When** the job fails, **Then** the `OperationRun` records the failure with error details and no stored report or findings are created.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 — Generate findings for high-privilege role assignments (Priority: P1)
|
||||
|
||||
As a compliance officer, I want the system to automatically generate findings for high-privilege Entra admin role assignments, so that I can track privileged access posture and identify excessive admin patterns.
|
||||
|
||||
**Why this priority**: Findings are the actionable output of the scan. Without them, there is no governance signal.
|
||||
|
||||
**Independent Test**: Run the finding generator for a tenant with a Global Administrator assignment; confirm a finding with `finding_type=entra_admin_roles`, `severity=critical`, and the correct fingerprint exists.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has 3 users assigned to "Global Administrator", **When** the finding generator runs, **Then** 3 individual findings are created (one per principal per role), each with `finding_type=entra_admin_roles`, `severity=critical`, and a deterministic fingerprint.
|
||||
2. **Given** a tenant has a user assigned to "Security Administrator" (high privilege, not Global Admin), **When** the finding generator runs, **Then** a finding is created with `severity=high`.
|
||||
3. **Given** a previously-flagged principal's high-privilege role assignment is removed, **When** the next scan runs the finding generator, **Then** the corresponding finding is auto-resolved (`status=resolved`, `resolved_at` set, `resolved_reason=role_assignment_removed`).
|
||||
4. **Given** the same principal still holds the same high-privilege role across two scans, **When** the finding generator runs again, **Then** no duplicate finding is created (fingerprint idempotency); `times_seen` and `last_seen_at` are updated.
|
||||
5. **Given** a tenant has more than 5 principals assigned to "Global Administrator", **When** the finding generator runs, **Then** an aggregate finding "Too many Global Admins" is created with `severity=high` and a distinct fingerprint.
|
||||
6. **Given** a resolved finding's principal is re-assigned the same high-privilege role, **When** the finding generator runs, **Then** the finding is re-opened (`status=new`, `resolved_at`/`resolved_reason` cleared, evidence updated).
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 — Alert on high-privilege admin role events (Priority: P2)
|
||||
|
||||
As a workspace manager, I want to receive alerts (Teams/email) when new high-privilege Entra admin role assignments are detected, so I can react to privilege escalation without polling the UI.
|
||||
|
||||
**Why this priority**: Alerts are the push-notification layer. They close the feedback loop for time-sensitive governance signals.
|
||||
|
||||
**Independent Test**: Create an alert rule for the new event type, run the finding generator with a new Global Admin assignment, and confirm a delivery is queued.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an alert rule exists for `entra.admin_roles.high` with minimum severity = high, **When** a new high-privilege finding is created, **Then** a delivery is queued for each enabled destination on that rule.
|
||||
2. **Given** an alert rule exists with minimum severity = critical, **When** a finding with severity = high (not critical) is created, **Then** no delivery is queued.
|
||||
3. **Given** the same finding persists across scans (no change), **When** the alert evaluator runs, **Then** cooldown/dedupe logic prevents duplicate notifications.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 — Entra permissions appear in permission posture (Priority: P2)
|
||||
|
||||
As a workspace operator, I want the new Entra-specific Graph permissions (`RoleManagement.Read.Directory`) to appear in the permission posture checks, so that the posture score accurately reflects whether a tenant can run admin role scans.
|
||||
|
||||
**Why this priority**: Without this, Spec 104's posture score would silently ignore whether the Entra roles feature is permission-ready — the posture would "lie."
|
||||
|
||||
**Independent Test**: Verify that after loading the merged registry (Intune + Entra), the required permission `RoleManagement.Read.Directory` appears in the list. Verify posture score computation includes it.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** `config/entra_permissions.php` defines `RoleManagement.Read.Directory` as required, **When** the registry loader runs, **Then** the merged required-permissions list includes the Entra entry alongside all Intune entries.
|
||||
2. **Given** a tenant does not have `RoleManagement.Read.Directory` granted, **When** the permission posture generator runs, **Then** a posture finding is created for that missing permission, and the posture score reflects the gap.
|
||||
3. **Given** a tenant has all Intune + Entra permissions granted, **When** posture runs, **Then** the posture score is 100.
|
||||
|
||||
---
|
||||
|
||||
### User Story 5 — View admin roles summary on tenant dashboard (Priority: P3)
|
||||
|
||||
As a workspace operator, I want to see a summary card on the tenant dashboard displaying the latest admin roles scan timestamp and high-privilege assignment count, so I can quickly assess a tenant's admin roles posture at a glance.
|
||||
|
||||
**Why this priority**: UI visibility provides daily governance context. Lower priority because the backend data and findings are more critical.
|
||||
|
||||
**Independent Test**: Navigate to a tenant dashboard after a scan has completed; confirm the card shows the correct timestamp and high-privilege count drawn from the latest stored report.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has a stored report with `report_type=entra.admin_roles`, **When** I view the tenant dashboard, **Then** the "Admin Roles" card displays the last scan timestamp and high-privilege assignment count from the latest report.
|
||||
2. **Given** a tenant has no admin roles report yet, **When** I view the tenant dashboard, **Then** the card shows "No scan performed" with a "Scan now" CTA (gated by `ENTRA_ROLES_MANAGE`).
|
||||
3. **Given** I click "Scan now" and have `ENTRA_ROLES_MANAGE`, **When** the button is pressed, **Then** a new `entra.admin_roles.scan` OperationRun is dispatched and the UI shows a pending state.
|
||||
4. **Given** I have `ENTRA_ROLES_VIEW` but not `ENTRA_ROLES_MANAGE`, **When** I view the card, **Then** the "Scan now" button is not visible and the scan action endpoint returns 403.
|
||||
|
||||
---
|
||||
|
||||
### User Story 6 — View full admin roles report (Priority: P3)
|
||||
|
||||
As a compliance officer, I want to view the full admin roles report (table of all high-privilege assignments, totals, role definitions) for a specific tenant, so I can audit detailed role assignment state.
|
||||
|
||||
**Why this priority**: Detailed report viewing supports audit workflows but is not required for core posture detection.
|
||||
|
||||
**Independent Test**: Navigate to the stored reports viewer filtered by `entra.admin_roles` for a tenant; confirm the report displays a table of high-privilege assignments with principal names, roles, and scope.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has a stored report with `report_type=entra.admin_roles`, **When** I view the report, **Then** I see a summary (totals) and a table of high-privilege role assignments with principal display name, principal type, role display name, and directory scope.
|
||||
2. **Given** multiple reports exist over time, **When** I list reports, **Then** they are ordered by creation date descending and I can navigate between them.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- **Graph API returns partial data**: If `roleDefinitions` succeeds but `roleAssignments` fails (or vice versa), the scan MUST fail the OperationRun (no partial reports). All-or-nothing per scan.
|
||||
- **Service principal assignments**: Some high-privilege role assignments go to service principals, not users. The finding generator MUST handle all principal types (`user`, `group`, `servicePrincipal`) and include the principal type in evidence.
|
||||
- **Scoped role assignments**: Some role assignments are scoped to administrative units (`directory_scope_id != "/"`). The high-privilege finding still applies — scope is recorded in evidence but does not downgrade severity in v1.
|
||||
- **Role definitions without `template_id`**: Custom role definitions may not have a `template_id`. The `HighPrivilegeRoleCatalog` falls back to `display_name` matching for classification. If neither matches, the role is not flagged as high-privilege.
|
||||
- **Tenant with zero role assignments**: Possible for app-only tenants. The scan produces a valid report with `assignments_total=0` and no findings.
|
||||
- **Concurrent scans for same tenant**: OperationRun active-run uniqueness constraint (per workspace_id, tenant_id, run_type) prevents duplicate concurrent scans.
|
||||
- **Threshold for "Too many Global Admins"**: v1 hardcodes threshold at 5. A `TODO` comment marks this for future settings-based configuration.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment (required):** This feature introduces Microsoft Graph calls and scheduled/queued work.
|
||||
- **Contract registry**: New Graph endpoints registered in `config/graph_contracts.php`:
|
||||
- `GET /roleManagement/directory/roleDefinitions`
|
||||
- `GET /roleManagement/directory/roleAssignments?$expand=principal`
|
||||
- **Safety gates**: Scan is a read-only operation against Entra (no writes). No confirmation required for scan dispatch — it's non-destructive.
|
||||
- **Tenant isolation**: Stored reports, findings, and OperationRuns are always scoped to a specific tenant via `tenant_id` FK. Cross-tenant data access is impossible at the query level.
|
||||
- **Run observability**: Each scan execution is tracked as an `OperationRun` with `type=entra.admin_roles.scan`, recording status, outcome, started_at, completed_at, and error details.
|
||||
- **Tests**: Unit tests for report generation, finding generation, high-privilege classification, auto-resolve, alert event production, and registry merge.
|
||||
|
||||
**Constitution alignment (RBAC-UX):**
|
||||
- **Authorization planes**: Tenant-context (`/admin/t/{tenant}/...`) for viewing reports and findings; workspace-context for alerts.
|
||||
- **404 vs 403**: Non-member / not entitled to workspace or tenant scope → 404 (deny-as-not-found). Member missing `ENTRA_ROLES_VIEW` → 403 for view. Member missing `ENTRA_ROLES_MANAGE` → 403 for scan.
|
||||
- **Server-side enforcement**: New policy for on-demand scan action; existing `FindingPolicy` covers posture findings.
|
||||
- **Capability registry**: New constants `ENTRA_ROLES_VIEW` and `ENTRA_ROLES_MANAGE` added to `App\Support\Auth\Capabilities`.
|
||||
- **Global search**: No new globally searchable resources.
|
||||
- **Destructive actions**: None. Scans are read-only; findings are system-generated.
|
||||
- **Authorization tests**: Positive test (user with `ENTRA_ROLES_VIEW` can see report; user with `ENTRA_ROLES_MANAGE` can trigger scan) and negative test (user without capability gets 403; non-member gets 404).
|
||||
|
||||
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable — no OIDC/SAML login flows involved.
|
||||
|
||||
**Constitution alignment (BADGE-001):**
|
||||
- `finding_type=entra_admin_roles` — new finding type value added to centralized `FindingTypeBadge` mapper with appropriate icon and color.
|
||||
- `severity` values: Uses existing `low`, `medium`, `high`, `critical` — no new severity values.
|
||||
- `status` values: Uses existing `new`, `acknowledged`, `resolved` — no new status values.
|
||||
- Tests cover rendering of `entra_admin_roles` type badge.
|
||||
|
||||
**Constitution alignment (Filament Action Surfaces):** This spec adds a small tenant card widget ("Admin Roles") with a "Scan now" header action and a "View latest report" link. No new full Resources or RelationManagers.
|
||||
|
||||
**Constitution alignment (UX-001 — Layout and Information Architecture):**
|
||||
- The tenant card widget follows existing card widget conventions (summary stat + CTA).
|
||||
- Report viewer reuses the existing stored reports viewer layout (existing UX-001-compliant screen).
|
||||
- **Exemption**: No new Create/Edit pages — scan is a single-action trigger. No form layout applies.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001 (Entra Permissions Registry)**: The system MUST provide a `config/entra_permissions.php` registry file following the same schema as `config/intune_permissions.php`, declaring `RoleManagement.Read.Directory` (type: application) as required for the `entra-admin-roles` feature. Optionally: `Directory.Read.All` as a fallback entry.
|
||||
- **FR-002 (Registry Merge)**: The registry loader (used by TenantPermissionService and posture generator) MUST merge entries from both `config/intune_permissions.php` and `config/entra_permissions.php` into a single required-permissions set. The merge MUST be non-breaking (existing Intune posture flows unchanged).
|
||||
- **FR-003 (Graph Contract Registration)**: New Graph endpoints MUST be registered in `config/graph_contracts.php`:
|
||||
- `GET /roleManagement/directory/roleDefinitions`
|
||||
- `GET /roleManagement/directory/roleAssignments?$expand=principal`
|
||||
- **FR-004 (Admin Roles Report Service)**: The system MUST provide `EntraAdminRolesReportService` that:
|
||||
- Fetches role definitions via Graph
|
||||
- Fetches role assignments (with `$expand=principal`) via Graph
|
||||
- Builds a structured payload (see Payload Schema below)
|
||||
- Computes a deterministic content fingerprint from sorted `(role_definition_template_id or id, principal_id, scope_id)` tuples
|
||||
- Writes a `stored_report` with `report_type=entra.admin_roles`, fingerprint, and `previous_fingerprint`
|
||||
- Deduplicates on identical fingerprint (no new report if data unchanged)
|
||||
- **FR-005 (Payload Schema)**: Stored report payload MUST contain: `provider_key`, `domain`, `measured_at`, `role_definitions` array, `role_assignments` array (with principal expansion), `totals` (roles_total, assignments_total, high_privilege_assignments), and `high_privilege` (definition_ids, count).
|
||||
- **FR-006 (High-Privilege Role Catalog)**: The system MUST provide a `HighPrivilegeRoleCatalog` class that classifies roles as high-privilege using a deterministic list. Classification MUST prefer `template_id` matching, falling back to `display_name` matching. v1 set: Global Administrator, Privileged Role Administrator, Security Administrator, Conditional Access Administrator, Exchange Administrator, Authentication Administrator.
|
||||
- **FR-007 (OperationRun Type)**: A new `OperationRunType` case `EntraAdminRolesScan` with value `entra.admin_roles.scan` MUST be added. Active-run uniqueness MUST be enforced per `(workspace_id, tenant_id, run_type)`.
|
||||
- **FR-008 (Scan Job)**: The system MUST provide a `ScanEntraAdminRolesJob` that:
|
||||
- Creates/updates an `OperationRun`
|
||||
- Calls `EntraAdminRolesReportService` to produce the report
|
||||
- Calls `EntraAdminRolesFindingGenerator` to produce findings
|
||||
- Records success/failure outcome on the OperationRun
|
||||
- **FR-009 (Finding Generator)**: The system MUST provide `EntraAdminRolesFindingGenerator` that creates findings with:
|
||||
- `finding_type = entra_admin_roles`
|
||||
- `source = entra.admin_roles`
|
||||
- For each high-privilege role assignment: one finding per (principal, role) pair
|
||||
- Severity: `critical` for Global Administrator, `high` for all other high-privilege roles
|
||||
- Fingerprint: `entra_admin_role:{tenant_id}:{role_template_or_id}:{principal_id}:{scope_id}`
|
||||
- Evidence: role display name, principal display name, principal type, scope, is_built_in
|
||||
- **FR-010 (Aggregate Finding — Too Many Global Admins)**: When the number of principals assigned to Global Administrator exceeds a threshold (v1 hardcoded: 5), the system MUST create an aggregate finding with:
|
||||
- Fingerprint: `entra_admin_role_ga_count:{tenant_id}`
|
||||
- Severity: `high`
|
||||
- Evidence: count, threshold, list of principal display names
|
||||
- **FR-011 (Idempotent Upsert)**: Finding generation MUST use fingerprint-based upsert to prevent duplicates. On repeated scans: `times_seen` and `last_seen_at` are updated. On data change: findings for removed assignments are auto-resolved; new assignments create new findings.
|
||||
- **FR-012 (Auto-Resolve)**: When a high-privilege role assignment no longer appears in the latest scan, the corresponding finding MUST be auto-resolved (`status=resolved`, `resolved_at` set, `resolved_reason=role_assignment_removed`). Auto-resolve applies to `new` and `acknowledged` findings alike (acknowledged metadata preserved).
|
||||
- **FR-013 (Re-Open)**: When a resolved finding's fingerprint matches a current assignment (re-assigned role), the finding MUST be re-opened (`status=new`, `resolved_at`/`resolved_reason` cleared, evidence updated).
|
||||
- **FR-014 (Alert Event)**: When high-privilege findings are created or re-opened, the system MUST produce events with `event_type=entra.admin_roles.high` for the alert dispatch pipeline. Events include tenant_id, finding fingerprint, severity. Resolved or unchanged findings do NOT produce alert events.
|
||||
- **FR-015 (Alert Rule Event Type)**: The `entra.admin_roles.high` event type MUST be available in the alert rule event type dropdown. Constant added to `AlertRule` model.
|
||||
- **FR-016 (Scan Scheduling)**: The scan MUST support two modes:
|
||||
- Scheduled: daily via workspace dispatcher (alongside existing scheduled scans)
|
||||
- On-demand: "Scan now" button on tenant dashboard (gated by `ENTRA_ROLES_MANAGE`)
|
||||
- **FR-017 (RBAC Capabilities)**: New capabilities `ENTRA_ROLES_VIEW` and `ENTRA_ROLES_MANAGE` MUST be added to the canonical `Capabilities` registry and mapped to roles (Readonly/Operator → view; Manager/Owner → manage).
|
||||
- **FR-018 (Skip Unconnected Tenants)**: Scan MUST gracefully skip tenants without a configured provider connection. No findings, reports, or error OperationRuns for unconnected tenants.
|
||||
- **FR-019 (Data Minimization)**: Stored report payload MUST contain only governance-relevant data: IDs, display names, principal types, and scopes. No tokens, secrets, or excessive PII.
|
||||
- **FR-020 (Tenant Card Widget)**: A tenant dashboard card MUST display: last scan timestamp, high-privilege assignment count, "Scan now" CTA (gated by capability), and "View latest report" link.
|
||||
- **FR-021 (Report Viewer)**: The existing stored reports viewer MUST support filtering by `report_type=entra.admin_roles` and display the report's summary + high-privilege assignments table.
|
||||
|
||||
## Payload Schema (stored_reports.payload)
|
||||
|
||||
```json
|
||||
{
|
||||
"provider_key": "microsoft",
|
||||
"domain": "entra",
|
||||
"measured_at": "2026-02-21T10:00:00Z",
|
||||
"role_definitions": [
|
||||
{
|
||||
"id": "...",
|
||||
"template_id": "...",
|
||||
"display_name": "Global Administrator",
|
||||
"is_built_in": true
|
||||
}
|
||||
],
|
||||
"role_assignments": [
|
||||
{
|
||||
"id": "...",
|
||||
"role_definition_id": "...",
|
||||
"directory_scope_id": "/",
|
||||
"principal": {
|
||||
"id": "...",
|
||||
"type": "user|group|servicePrincipal",
|
||||
"display_name": "..."
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"roles_total": 100,
|
||||
"assignments_total": 42,
|
||||
"high_privilege_assignments": 7
|
||||
},
|
||||
"high_privilege": {
|
||||
"definition_ids": ["..."],
|
||||
"assignments": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||
|
||||
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Tenant Card "Admin Roles" (Widget) | Tenant Dashboard | "Scan now" (gated by `ENTRA_ROLES_MANAGE`, non-destructive → no confirmation) | N/A | N/A | N/A | "No scan performed — Scan now" (gated) | N/A | N/A | OperationRun tracks scan | Widget, not a full Resource |
|
||||
| Report Viewer (existing) | Stored Reports > View (filtered `entra.admin_roles`) | N/A (uses existing report viewer) | Table of high-privilege assignments | N/A | N/A | "No reports yet" | N/A | N/A | No (read-only) | Reuses existing viewer, no new page |
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **StoredReport (extended)**: Existing model gains a new `report_type` constant `entra.admin_roles`. Payload schema documented above. Fingerprint-based dedupe and `previous_fingerprint` for drift detection.
|
||||
- **Finding (extended)**: Existing model gains `finding_type=entra_admin_roles` and `source=entra.admin_roles`. Each finding represents a high-privilege role assignment (per principal per role) or an aggregate condition (too many Global Admins). Uses existing severity/status/fingerprint infrastructure.
|
||||
- **AlertRule (extended)**: Existing model gains `EVENT_ENTRA_ADMIN_ROLES_HIGH` constant for `event_type=entra.admin_roles.high`. No schema change.
|
||||
- **HighPrivilegeRoleCatalog (new)**: A value object / catalog class that deterministically classifies Entra role definitions as high-privilege. Uses `template_id` (preferred) or `display_name` (fallback). Testable and extensible.
|
||||
- **EntraAdminRolesReportService (new)**: Service class responsible for fetching Graph data, building the payload, computing fingerprint, and writing the stored report.
|
||||
- **EntraAdminRolesFindingGenerator (new)**: Service class responsible for generating/upserting findings from report data.
|
||||
- **ScanEntraAdminRolesJob (new)**: Queued job orchestrating the OperationRun lifecycle, report service, and finding generator.
|
||||
- **OperationRunType (extended)**: Enum gains `EntraAdminRolesScan = 'entra.admin_roles.scan'`.
|
||||
- **Capabilities (extended)**: `ENTRA_ROLES_VIEW = 'entra_roles.view'` and `ENTRA_ROLES_MANAGE = 'entra_roles.manage'`.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No PIM/Eligible Assignments (`RoleEligibilitySchedules` etc.) in v1.
|
||||
- No automatic remediation (only evidence + findings + alerts).
|
||||
- No new `EvidenceItems` model (comes later; we use `stored_reports`).
|
||||
- No tenancy/RBAC refactor.
|
||||
- No custom role definitions management.
|
||||
|
||||
## High-Privilege Role Classification (v1)
|
||||
|
||||
The `HighPrivilegeRoleCatalog` classifies roles using `template_id` (preferred) with fallback to `display_name`:
|
||||
|
||||
| Role Display Name | Template ID | Severity when assigned |
|
||||
|---|---|---|
|
||||
| Global Administrator | `62e90394-69f5-4237-9190-012177145e10` | critical |
|
||||
| Privileged Role Administrator | `e8611ab8-c189-46e8-94e1-60213ab1f814` | high |
|
||||
| Security Administrator | `194ae4cb-b126-40b2-bd5b-6091b380977d` | high |
|
||||
| Conditional Access Administrator | `b1be1c3e-b65d-4f19-8427-f6fa0d97feb9` | high |
|
||||
| Exchange Administrator | `29232cdf-9323-42fd-ade2-1d097af3e4de` | high |
|
||||
| Authentication Administrator | `c4e39bd9-1100-46d3-8c65-fb160da0071f` | high |
|
||||
|
||||
This set can be extended later via workspace settings.
|
||||
|
||||
## Graph API Details
|
||||
|
||||
### Required Permissions (Application)
|
||||
|
||||
- **Least privilege (v1)**: `RoleManagement.Read.Directory`
|
||||
- **Fallback (if $expand=principal requires it)**: `Directory.Read.All`
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /roleManagement/directory/roleDefinitions` — all role definitions for the directory
|
||||
- `GET /roleManagement/directory/roleAssignments?$expand=principal` — all active role assignments with principal details
|
||||
|
||||
## Clarifications
|
||||
|
||||
### Session 2026-02-21
|
||||
|
||||
- Q: Does this spec introduce new database tables? → A: No. Reuses `stored_reports` (Spec 104) and `findings` (existing). No migration needed beyond OperationRunType enum case.
|
||||
- Q: Is `$expand=principal` reliable for all principal types? → A: Yes — Graph returns principal details for users, groups, and service principals. If expansion fails for a specific principal, the assignment is still recorded with `principal.display_name = null` (evidence captures this gap).
|
||||
- Q: Why two capabilities (VIEW/MANAGE) instead of reusing existing findings capabilities? → A: The scan action is domain-specific (Entra roles) and should be independently gatable. Viewing the resulting findings still uses existing findings capabilities, but triggering the scan and viewing the admin roles report card uses the new capabilities.
|
||||
- Q: How is daily scheduling implemented? → A: Same pattern as existing scheduled scans — workspace dispatcher iterates tenants with active connections and dispatches `ScanEntraAdminRolesJob` per tenant.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001 (Evidence completeness)**: For any tenant with a configured provider connection and `RoleManagement.Read.Directory` permission, 100% of active directory role assignments are captured in the stored report.
|
||||
- **SC-002 (Finding accuracy)**: The number of open `entra_admin_roles` findings for a tenant exactly matches the number of high-privilege role assignments in the latest scan (no duplicates, no omissions).
|
||||
- **SC-003 (Auto-resolve latency)**: When a high-privilege role assignment is removed, the corresponding finding is resolved within the next scan cycle.
|
||||
- **SC-004 (Alert delivery)**: When a new high-privilege finding is created and an active alert rule matches, a delivery is queued within 2 minutes.
|
||||
- **SC-005 (Posture integration)**: After deploying this spec, the permission posture score for tenants missing `RoleManagement.Read.Directory` reflects the gap (score decreases proportionally).
|
||||
- **SC-006 (Temporal audit)**: An operator can query stored reports to see a tenant's admin role posture at any point within the retention window (default 90 days).
|
||||
- **SC-007 (No duplicates)**: Repeated scans with identical data produce no duplicate findings or stored reports.
|
||||
- **SC-008 (Scan performance)**: A single tenant scan completes within 30 seconds for tenants with up to 200 role assignments.
|
||||
Loading…
Reference in New Issue
Block a user