## Summary Automated scanning of Entra ID directory roles to surface high-privilege role assignments as trackable findings with alerting support. ## What's included ### Core Services - **EntraAdminRolesReportService** — Fetches role definitions + assignments via Graph API, builds payload with fingerprint deduplication - **EntraAdminRolesFindingGenerator** — Creates/resolves/reopens findings based on high-privilege role catalog - **HighPrivilegeRoleCatalog** — Curated list of high-privilege Entra roles (Global Admin, Privileged Auth Admin, etc.) - **ScanEntraAdminRolesJob** — Queued job orchestrating scan → report → findings → alerts pipeline ### UI - **AdminRolesSummaryWidget** — Tenant dashboard card showing last scan time, high-privilege assignment count, scan trigger button - RBAC-gated: `ENTRA_ROLES_VIEW` for viewing, `ENTRA_ROLES_MANAGE` for scan trigger ### Infrastructure - Graph contracts for `entraRoleDefinitions` + `entraRoleAssignments` - `config/entra_permissions.php` — Entra permission registry - `StoredReport.fingerprint` migration (deduplication support) - `OperationCatalog` label + duration for `entra.admin_roles.scan` - Artisan command `entra:scan-admin-roles` for CLI/scheduled use ### Global UX improvement - **SummaryCountsNormalizer**: Zero values filtered, snake_case keys humanized (e.g. `report_deduped: 1` → `Report deduped: 1`). Affects all operation notifications. ## Test Coverage - **12 test files**, **79+ tests**, **307+ assertions** - Report service, finding generator, job orchestration, widget rendering, alert integration, RBAC enforcement, badge mapping ## Spec artifacts - `specs/105-entra-admin-roles-evidence-findings/tasks.md` — Full task breakdown (38 tasks, all complete) - `specs/105-entra-admin-roles-evidence-findings/checklists/requirements.md` — All items checked ## Files changed 46 files changed, 3641 insertions(+), 15 deletions(-) Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #128
31 KiB
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.highevent 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 (newreport_type=entra.admin_roles)findings— tenant-owned, extended withfinding_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 Admin Roles card + report viewer),ENTRA_ROLES_MANAGE(trigger on-demand scan) entra_admin_rolesfindings in the Findings list are gated by existingFINDINGS_VIEW—ENTRA_ROLES_VIEWis NOT required to see them- Readonly/Operator roles →
ENTRA_ROLES_VIEW - Manager/Owner roles →
ENTRA_ROLES_MANAGE - Findings remain gated by existing
TENANT_FINDINGS_ACKNOWLEDGEfor acknowledge actions
Assumptions and Dependencies
- Spec 104 (Provider Permission Posture) established the
stored_reportstable,StoredReportmodel, fingerprint-based dedupe, and the posture report pattern. This spec reuses that infrastructure with a newreport_type. - StoredReports retention is handled by existing infrastructure and defaults to 90 days.
- Alerts v1 (Spec 099) provides the generic
AlertDispatchService,EvaluateAlertsJobframework, andAlertRulemodel. 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 (includingresolved), and fingerprint-based idempotent upsert (established by drift findings + Spec 104). config/intune_permissions.phpis the existing registry for Intune-specific Graph permissions. A newconfig/entra_permissions.phpwill be created for Entra Directory permissions. The registry loader (TenantPermissionServiceor equivalent) must merge both sources.- Microsoft Graph unified RBAC API provides
roleManagement/directory/roleDefinitionsandroleManagement/directory/roleAssignments?$expand=principalfor 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:
- Given a tenant has a configured provider connection with
RoleManagement.Read.Directorypermission, When the admin roles scan runs, Then astored_reportis created withreport_type=entra.admin_roles,tenant_idset, and a JSONB payload containingrole_definitions,role_assignments, andtotals. - 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).
- 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_fingerprintreferences the prior report's fingerprint. - Given the tenant has no provider connection, When the scan is dispatched, Then the job completes gracefully with no stored report or findings created.
- Given the Graph API is unreachable during the scan, When the job fails, Then the
OperationRunrecords 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:
- 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. - 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. - 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_atset,resolved_reason=role_assignment_removed). - 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_seenandlast_seen_atare updated. - 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=highand a distinct fingerprint. - 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_reasoncleared, 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:
- Given an alert rule exists for
entra.admin_roles.highwith minimum severity = high, When a new high-privilege finding is created, Then a delivery is queued for each enabled destination on that rule. - Given an alert rule exists with minimum severity = critical, When a finding with severity = high (not critical) is created, Then no delivery is queued.
- 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:
- Given
config/entra_permissions.phpdefinesRoleManagement.Read.Directoryas required, When the registry loader runs, Then the merged required-permissions list includes the Entra entry alongside all Intune entries. - Given a tenant does not have
RoleManagement.Read.Directorygranted, When the permission posture generator runs, Then a posture finding is created for that missing permission, and the posture score reflects the gap. - 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:
- 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. - 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). - Given I click "Scan now" and have
ENTRA_ROLES_MANAGE, When the button is pressed, Then a newentra.admin_roles.scanOperationRun is dispatched and the UI shows a pending state. - Given I have
ENTRA_ROLES_VIEWbut notENTRA_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:
- 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. - 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
roleDefinitionssucceeds butroleAssignmentsfails (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. - Group-assigned roles: When a group holds a high-privilege role, it counts as one principal (no member expansion in v1). The finding is created for the group itself. The aggregate "Too many Global Admins" threshold counts assignment records, not expanded members. Evidence records
principal.type=groupso operators can identify group-based assignments for manual review. - 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 atemplate_id. TheHighPrivilegeRoleCatalogfalls back todisplay_namematching 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=0and 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
TODOcomment 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/roleDefinitionsGET /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_idFK. Cross-tenant data access is impossible at the query level. - Run observability: Each scan execution is tracked as an
OperationRunwithtype=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 Admin Roles card/report. Member missingENTRA_ROLES_MANAGE→ 403 for scan. Findings list uses existingFINDINGS_VIEW(noENTRA_ROLES_VIEWrequired). - Server-side enforcement: New policy for on-demand scan action; existing
FindingPolicycovers findings (gated byFINDINGS_VIEW, notENTRA_ROLES_VIEW). - Capability registry: New constants
ENTRA_ROLES_VIEWandENTRA_ROLES_MANAGEadded toApp\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_VIEWcan see report; user withENTRA_ROLES_MANAGEcan 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 centralizedFindingTypeBadgemapper with appropriate icon and color.severityvalues: Uses existinglow,medium,high,critical— no new severity values.statusvalues: Uses existingnew,acknowledged,resolved— no new status values.- Tests cover rendering of
entra_admin_rolestype 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.phpregistry file following the same schema asconfig/intune_permissions.php, declaringRoleManagement.Read.Directory(type: application) as required for theentra-admin-rolesfeature. Optionally:Directory.Read.Allas a fallback entry. - FR-002 (Registry Merge): The registry loader (used by TenantPermissionService and posture generator) MUST merge entries from both
config/intune_permissions.phpandconfig/entra_permissions.phpinto 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/roleDefinitionsGET /roleManagement/directory/roleAssignments?$expand=principal
- FR-004 (Admin Roles Report Service): The system MUST provide
EntraAdminRolesReportServicethat:- 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_reportwithreport_type=entra.admin_roles, fingerprint, andprevious_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_definitionsarray,role_assignmentsarray (with principal expansion),totals(roles_total, assignments_total, high_privilege_assignments), andhigh_privilege(definition_ids, count). - FR-006 (High-Privilege Role Catalog): The system MUST provide a
HighPrivilegeRoleCatalogclass that classifies roles as high-privilege using a deterministic list. Classification MUST prefertemplate_idmatching, falling back todisplay_namematching. v1 set: Global Administrator, Privileged Role Administrator, Security Administrator, Conditional Access Administrator, Exchange Administrator, Authentication Administrator. - FR-007 (OperationRun Type): A new
OperationRunTypecaseEntraAdminRolesScanwith valueentra.admin_roles.scanMUST be added. Active-run uniqueness MUST be enforced per(workspace_id, tenant_id, run_type). - FR-008 (Scan Job): The system MUST provide a
ScanEntraAdminRolesJobthat:- Creates/updates an
OperationRun - Calls
EntraAdminRolesReportServiceto produce the report - Calls
EntraAdminRolesFindingGeneratorto produce findings - Records success/failure outcome on the OperationRun
- Creates/updates an
- FR-009 (Finding Generator): The system MUST provide
EntraAdminRolesFindingGeneratorthat creates findings with:finding_type = entra_admin_rolessource = entra.admin_roles- For each high-privilege role assignment: one finding per (principal, role) pair
- Severity:
criticalfor Global Administrator,highfor 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
- Fingerprint:
- FR-011 (Idempotent Upsert): Finding generation MUST use fingerprint-based upsert to prevent duplicates. On repeated scans:
times_seenandlast_seen_atare 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_atset,resolved_reason=role_assignment_removed). Auto-resolve applies tonewandacknowledgedfindings 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_reasoncleared, 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.highfor 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.highevent type MUST be available in the alert rule event type dropdown. Constant added toAlertRulemodel. - 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_VIEWandENTRA_ROLES_MANAGEMUST be added to the canonicalCapabilitiesregistry 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_rolesand display the report's summary + high-privilege assignments table.
Payload Schema (stored_reports.payload)
{
"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_typeconstantentra.admin_roles. Payload schema documented above. Fingerprint-based dedupe andprevious_fingerprintfor drift detection. - Finding (extended): Existing model gains
finding_type=entra_admin_rolesandsource=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_HIGHconstant forevent_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) ordisplay_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'andENTRA_ROLES_MANAGE = 'entra_roles.manage'.
Non-Goals
- No PIM/Eligible Assignments (
RoleEligibilitySchedulesetc.) in v1. - No automatic remediation (only evidence + findings + alerts).
- No new
EvidenceItemsmodel (comes later; we usestored_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 directoryGET /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) andfindings(existing). - Q: Does this spec require database migrations? → A: Yes — one migration adds
fingerprintandprevious_fingerprint(plus indexes) tostored_reportsfor report dedupe and chaining, andOperationRunTypegains theentra.admin_roles.scancase. - Q: Is
$expand=principalreliable 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 withprincipal.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
ScanEntraAdminRolesJobper tenant. - Q: When a group is assigned a high-privilege role, does the finding generator expand group members or treat the group as one principal? → A: Group = 1 principal. No member expansion in v1. Findings and the aggregate GA threshold count reflect Graph roleAssignment records directly (1 assignment = 1 finding). Member expansion would require additional Graph calls +
GroupMember.Read.Allpermission and is deferred to a future spec. - Q: Should
ENTRA_ROLES_VIEWgateentra_admin_rolesfindings in the Findings list too, or only the Admin Roles card + report viewer? → A: Card + report only.ENTRA_ROLES_VIEWgates the Admin Roles dashboard card and the stored-report viewer. Findings of typeentra_admin_rolesin the Findings list remain gated by the existingFINDINGS_VIEWcapability (no dual-gate).
Success Criteria (mandatory)
Measurable Outcomes
- SC-001 (Evidence completeness): For any tenant with a configured provider connection and
RoleManagement.Read.Directorypermission, 100% of active directory role assignments are captured in the stored report. - SC-002 (Finding accuracy): The number of open
entra_admin_rolesfindings 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.Directoryreflects 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 configured 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.