TenantAtlas/specs/105-entra-admin-roles-evidence-findings/spec.md

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.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 Admin Roles card + report viewer), ENTRA_ROLES_MANAGE (trigger on-demand scan)
    • entra_admin_roles findings in the Findings list are gated by existing FINDINGS_VIEWENTRA_ROLES_VIEW is NOT required to see them
    • 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.
  • 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=group so 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 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 Admin Roles card/report. Member missing ENTRA_ROLES_MANAGE → 403 for scan. Findings list uses existing FINDINGS_VIEW (no ENTRA_ROLES_VIEW required).
  • Server-side enforcement: New policy for on-demand scan action; existing FindingPolicy covers findings (gated by FINDINGS_VIEW, not ENTRA_ROLES_VIEW).
  • 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)

{
  "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.
  • 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.All permission and is deferred to a future spec.
  • Q: Should ENTRA_ROLES_VIEW gate entra_admin_roles findings in the Findings list too, or only the Admin Roles card + report viewer? → A: Card + report only. ENTRA_ROLES_VIEW gates the Admin Roles dashboard card and the stored-report viewer. Findings of type entra_admin_roles in the Findings list remain gated by the existing FINDINGS_VIEW capability (no dual-gate).

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.