Control: 5.3.3 Ensure 'Access reviews' for privileged roles are configured
Description
Access reviews enable administrators to establish an efficient automated process for reviewing group memberships, access to enterprise applications, and role assignments. These reviews can be scheduled to recur regularly, with flexible options for delegating the task of reviewing membership to different members of the organization.
Ensure Access reviews for high privileged Entra ID roles are done monthly or more frequently. These reviews should include at a minimum the roles listed below:
- Global Administrator
- Exchange Administrator
- SharePoint Administrator
- Teams Administrator
- Security Administrator
Note: An access review is created for each role selected after completing the process.
Remediation
To remediate using the UI:
- Navigate to
Microsoft Entra admin centerhttps://entra.microsoft.com/. - Click to expand
Identity Governanceand selectPrivileged Identity Management - Select
Microsoft Entra Rolesunder Manage. - Select
Access reviewsand clickNew access review.- Provide a name and description.
- Set
FrequencytoMonthlyor more frequently. - Set
Duration (in days)to at most4. - Set
EndtoNever. - Set
Users scopetoAll users and groups. - In
Roleselect these roles:Global Administrator,Exchange Administrator,SharePoint Administrator,Teams Administrator,Security Administrator - Set
Assignment typetoAll active and eligible assignments. - Set
Reviewersmember(s) responsible for this type of review, other than self.
- Upon completion settings:
- Set
Auto apply results to resourcetoEnable. - Set
If reviewers don't respondtoNo change.
- Set
- Advanced settings:
- Set
Show recommendationstoEnable. - Set
Require reason on approvaltoEnable. - Set
Mail notificationstoEnable. - Set
ReminderstoEnable.
- Set
- Click
Startto save the review.
Warning: Care should be taken when configuring the If reviewers don't respond setting for Global Administrator reviews, if misconfigured break-glass accounts could automatically have roles revoked. Additionally, reviewers should be educated on the purpose of break-glass accounts to prevent accidental manual removal of roles.
Default Value
By default access reviews are not configured.
Usage
Run the control in your terminal:
powerpipe control run microsoft365_compliance.control.cis_v500_5_3_3Snapshot and share results via Turbot Pipes:
powerpipe loginpowerpipe control run microsoft365_compliance.control.cis_v500_5_3_3 --shareSQL
This control uses a named query:
with tenant_list as ( select distinct on (tenant_id) tenant_id, _ctx from azuread_user),privileged_roles as ( select tenant_id, id, display_name from azuread_directory_role_definition where display_name in ( 'Global Administrator', 'Exchange Administrator', 'SharePoint Administrator', 'Teams Administrator', 'Security Administrator' )),privileged_role_reviews as ( select ar.tenant_id, d.id from azuread_access_review_schedule_definition ar, jsonb_array_elements(ar.scope -> 'resourceScopes') as r join azuread_directory_role_definition d on split_part(r ->> 'query', 'roleDefinitions/', 2) = d.id where (ar.settings -> 'mailNotificationsEnabled')::bool and (ar.settings -> 'reminderNotificationsEnabled')::bool and (ar.settings -> 'justificationRequiredOnApproval')::bool and ar.settings ->> 'defaultDecision' = 'Deny'),role_check as ( select tenant_id, array_agg(distinct id)::text[] as roles_reviewed from privileged_role_reviews group by tenant_id)select t.tenant_id as resource, case when array(select id from privileged_roles where tenant_id = t.tenant_id) <@ coalesce(r.roles_reviewed, '{}') then 'ok' else 'alarm' end as status, case when array(select id from privileged_roles where tenant_id = t.tenant_id) <@ coalesce(r.roles_reviewed, '{}') then t.tenant_id || ' has access reviews configured monthly (or more frequently) for all high-privileged roles (Global Administrator, Exchange Administrator, SharePoint Administrator, Teams Administrator, Security Administrator).' else t.tenant_id || ' does not have access reviews configured monthly for all high-privileged roles. Missing: ' || array_to_string( array( select pr.display_name from privileged_roles pr where pr.tenant_id = t.tenant_id and pr.id not in ( select unnest(coalesce(r.roles_reviewed, '{}')) ) ), ', ' ) end as reason , t.tenant_id as tenant_id from tenant_list as t left join role_check as r on r.tenant_id = t.tenant_id;