Back to Checks
m365

Service principals with privileged Entra directory roles must have no owners

entra_service_principal_privileged_role_no_owners
Service: entraSeverity: critical
OfficialBuilt-inVerified
Categories:identity-access
Resource Type:NotDefined

Microsoft Entra service principals holding permanent Control Plane (Tier 0) directory roles (such as Global Administrator or Privileged Role Administrator) are evaluated for the presence of owners on either the service principal itself or its parent app registration.

Risk

An owner of a service principal or its parent app registration can rotate credentials and sign in as the service principal, inheriting its Tier 0 role outside PIM and Conditional Access controls. This is a documented privilege escalation path impacting confidentiality, integrity, and availability of the tenant's control plane.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_service_principal_privileged_role_no_owners

Recommendation

Remove all owners from service principals that hold privileged Entra directory roles. Manage privileged service principals exclusively via PIM-eligible role assignments and break-glass controls. Ensure no human account can silently inherit control-plane privileges through ownership.

Remediation

Other

1Sign in to the Microsoft Entra admin center (https://entra.microsoft.com)
2Go to Identity > Applications > Enterprise applications > select the service principal
3Under Owners, remove all owners
4Repeat for the parent App Registration under Identity > Applications > App registrations
5Use PIM eligible assignments instead of permanent role assignments where possible

Check Code

"""Check for service principals with privileged roles that have owners."""

from typing import List

from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client


class entra_service_principal_privileged_role_no_owners(Check):
    """Service principal with a permanent Tier 0 directory role has no owners.

    Owners of a service principal or its parent app registration can rotate
    credentials and sign in as the service principal, inheriting its privileged
    directory role outside PIM approval flows and Conditional Access policies
    targeting user accounts.

    - PASS: The service principal does not hold a permanent Tier 0 directory
      role, or it does but has zero owners on both the service principal and
      its parent app registration.
    - FAIL: The service principal holds a permanent Tier 0 directory role and
      has at least one owner on either the service principal or its parent
      app registration.
    """

    def execute(self) -> List[CheckReportM365]:
        """Execute the privileged service principal owner check.

        Returns:
            A list of reports, one per service principal owned by the audited
            tenant.
        """
        findings = []
        for sp in entra_client.service_principals.values():
            report = CheckReportM365(
                metadata=self.metadata(),
                resource=sp,
                resource_name=sp.name,
                resource_id=sp.id,
            )

            if not sp.directory_role_template_ids:
                report.status = "PASS"
                report.status_extended = (
                    f"Service principal '{sp.name}' has no permanent Tier 0 "
                    f"directory role assignments."
                )
                findings.append(report)
                continue

            unique_owners = set(sp.sp_owner_ids) | set(sp.app_owner_ids)
            tier0_role_count = len(sp.directory_role_template_ids)

            if unique_owners:
                report.status = "FAIL"
                report.status_extended = (
                    f"Service principal '{sp.name}' holds {tier0_role_count} "
                    f"permanent Tier 0 directory role(s) and has "
                    f"{len(unique_owners)} owner(s) "
                    f"({len(sp.sp_owner_ids)} on the service principal, "
                    f"{len(sp.app_owner_ids)} on the parent app registration)."
                )
            else:
                report.status = "PASS"
                report.status_extended = (
                    f"Service principal '{sp.name}' holds {tier0_role_count} "
                    f"permanent Tier 0 directory role(s) and has no owners on "
                    f"either the service principal or its parent app registration."
                )

            findings.append(report)
        return findings