feat(m365/entra): add entra_conditional_access_policy_no_deleted_object_references check#11236
Conversation
|
✅ Conflict Markers Resolved All conflict markers have been successfully resolved in this pull request. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #11236 +/- ##
==========================================
- Coverage 93.96% 88.04% -5.93%
==========================================
Files 236 134 -102
Lines 34777 5804 -28973
==========================================
- Hits 32678 5110 -27568
+ Misses 2099 694 -1405
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…ct_references check Audits every Conditional Access policy for references to users, groups, or directory roles that no longer resolve in Microsoft Entra ID. Stale references silently change runtime policy behavior — include* references shrink enforcement scope, exclude* references can alter exemption logic — which is a known root cause of "we thought MFA was required but it wasn't" incidents. The implementation follows the eager-init pattern from prowler-cloud#11232: - A new resolver method `_resolve_directory_object_references` runs at service init time after the main `asyncio.gather`, walks every loaded Conditional Access policy, deduplicates identifiers per type, and queries Graph for each one (`/users/{id}`, `/groups/{id}`, `/roleManagement/directory/roleDefinitions/{id}`). Sentinel values (`All`, `None`, `GuestsOrExternalUsers`) are skipped before any Graph call. - Only HTTP 404 responses (or `Request_ResourceNotFound` error code) flag an identifier as deleted. Transient errors (5xx, throttling) are logged and skipped so the check never produces false positives on network blips. - Result is cached on the entra client as `unresolved_directory_object_references: Set[Tuple[str, str]]`, which the new pure-sync check reads to emit findings. - The check runs against all policies regardless of state — disabled-policy references are a misconfiguration that becomes live the moment the policy is re-enabled. Fix prowler-cloud#11063
…ANGELOG Completes prowler-cloud#11063 by: - Adding `_resolve_directory_object_references` and helper `_resolve_identifiers_for_type` to `entra_service.py`. - Hooking the resolver into `Entra.__init__` as a second-phase `loop.run_until_complete` after the main asyncio.gather, populating `self.unresolved_directory_object_references: Set[Tuple[str, str]]`. - Adding the CHANGELOG entry for the new check. Fix prowler-cloud#11063
c6e127d to
83f8c0b
Compare
|
Quick update — I rebased onto current master to pull in #11234 (the Dockerfile The rebase introduced zero code changes (only the new master commits land underneath the two feature commits). Could a maintainer kindly approve the workflow runs so CI can validate? Thanks! |
Context
Conditional Access policies that reference deleted users, groups, or roles silently change runtime enforcement: stale
include*references shrink scope, staleexclude*references can alter exemption logic. This is a well-known root cause of "we thought MFA was required but it wasn't" incidents. This PR adds a Prowler check that surfaces those stale references.Fix #11063
Description
entra_conditional_access_policy_no_deleted_object_referencesiterates every Conditional Access policy (regardless of state, per the issue spec) and reports identifiers inincludeUsers/excludeUsers/includeGroups/excludeGroups/includeRoles/excludeRolesthat no longer resolve in Microsoft Entra ID.asyncio.gatherinEntra.__init__, a secondloop.run_until_completecalls_resolve_directory_object_references, which walks the just-loadedconditional_access_policies, deduplicates referenced identifiers per type, skips the sentinel valuesAll,None, andGuestsOrExternalUsers, and queries Microsoft Graph for each one. The result is cached on the entra client asunresolved_directory_object_references: Set[Tuple[str, str]]. Sync checks read this attribute without making any Graph calls of their own./users/{id},/groups/{id},/roleManagement/directory/roleDefinitions/{id}. No new permissions beyond Prowler's M365 baseline (Directory.Read.All).HTTP 404(status code orRequest_ResourceNotFounderror code) flags an identifier as deleted. Transient errors (5xx, throttling, network failures) are logged and skipped — they must not produce false-positive FAILs per the spec.identity-access+e3, ResourceGroupIAM.Steps to review
prowler/providers/m365/services/entra/entra_conditional_access_policy_no_deleted_object_references/follows the layout of the recently mergedentra_app_registration_client_secret_unusedcheck (PR feat(m365/entra): add entra_app_registration_client_secret_unused check (consolidates #11097 and #11212) #11232).entra_service.py:Setto the typing import._resolve_directory_object_referencesand helper_resolve_identifiers_for_typemethods.loop.run_until_completeinEntra.__init__between the main attribute unpacking and theif created_loopcleanup.tests/providers/m365/services/entra/entra_conditional_access_policy_no_deleted_object_references/) cover 8 scenarios: no policies; sentinel-only references; all references resolve; deleted user in include; deleted group in exclude; deleted role in a disabled policy (still FAILs); orphans of all three types in one policy; multi-policy mixed PASS/FAIL. Tests follow the merged-PR pattern — pureunittest.mockagainstentra_client, no Graph mocking in check tests.5.28.0Added.Full M365 regression suite was run locally (
pytest tests/providers/m365/) — 841 passed, 0 failures.Checklist
Community Checklist
feature-request,good first issue,new-check,provider/m365).SDK/CLI
Directory.Read.All(already required for the entra service) grants/users/{id},/groups/{id}, and/roleManagement/directory/roleDefinitions/{id}reads.License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.