Back to Checks
m365

Conditional Access requires compliant device OR hybrid joined device OR MFA for admins or all users

entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required
Service: entraSeverity: high
OfficialBuilt-inVerified
Categories:identity-accesse3
Resource Type:NotDefined

Microsoft Entra Conditional Access policy enforces one of the following grant controls for admin roles or all users across all cloud apps: - 'Require device to be marked as compliant' - 'Require Microsoft Entra hybrid joined device' - 'Require multifactor authentication' This ensures that access is provided only under strong authentication or trusted device conditions.

Risk

If this policy is not implemented, attackers with compromised credentials may gain access from unmanaged devices or without strong authentication, increasing the likelihood of unauthorized access and data breaches.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required

Recommendation

Enforce a Conditional Access baseline where admins or all users must satisfy at least one strong control: compliant device, hybrid joined device, or MFA.

Remediation

Other

1Navigate to Microsoft Entra admin center https://entra.microsoft.com.
2Go to Protection > Conditional Access > Policies and create or edit a policy.
3Under Users, include All users or administrative roles.
4Under Target resources, include All cloud apps.
5Under Grant, select Grant access and enable these controls: Require multifactor authentication, Require device to be marked as compliant, and Require Microsoft Entra hybrid joined device.
6Set Grant operator to Require one of the selected controls.
7Start in Report-only mode for validation and then switch to On.

Check Code

from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
    AdminRoles,
    ConditionalAccessGrantControl,
    ConditionalAccessPolicyState,
    GrantControlOperator,
)

REQUIRED_GRANT_CONTROLS = {
    ConditionalAccessGrantControl.MFA,
    ConditionalAccessGrantControl.COMPLIANT_DEVICE,
    ConditionalAccessGrantControl.DOMAIN_JOINED_DEVICE,
}
ADMIN_ROLE_IDS = {role.value for role in AdminRoles}


class entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required(
    Check
):
    """Check that CA enforces compliant or hybrid joined device or MFA for admins/all users."""

    def _targets_admins_or_all_users(self, policy) -> bool:
        if "All" in policy.conditions.user_conditions.included_users:
            return True

        included_roles = set(policy.conditions.user_conditions.included_roles)
        return bool(ADMIN_ROLE_IDS.intersection(included_roles))

    def execute(self) -> list[CheckReportM365]:
        findings = []

        report = CheckReportM365(
            metadata=self.metadata(),
            resource={},
            resource_name="Conditional Access Policies",
            resource_id="conditionalAccessPolicies",
        )
        report.status = "FAIL"
        report.status_extended = "No Conditional Access Policy requires compliant device, hybrid joined device, or MFA for admin roles or all users across all cloud apps."

        for policy in entra_client.conditional_access_policies.values():
            if policy.state == ConditionalAccessPolicyState.DISABLED:
                continue

            if not self._targets_admins_or_all_users(policy):
                continue

            if (
                "All"
                not in policy.conditions.application_conditions.included_applications
            ):
                continue

            policy_grant_controls = set(policy.grant_controls.built_in_controls)
            if not REQUIRED_GRANT_CONTROLS.issubset(policy_grant_controls):
                continue

            if policy.grant_controls.operator != GrantControlOperator.OR:
                continue

            report = CheckReportM365(
                metadata=self.metadata(),
                resource=policy,
                resource_name=policy.display_name,
                resource_id=policy.id,
            )

            if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
                report.status = "FAIL"
                report.status_extended = f"Conditional Access Policy {policy.display_name} reports compliant device, hybrid joined device, or MFA for admin roles or all users but does not enforce it."
            else:
                report.status = "PASS"
                report.status_extended = f"Conditional Access Policy {policy.display_name} enforces compliant device, hybrid joined device, or MFA for admin roles or all users across all cloud apps."
                break

        findings.append(report)
        return findings