Back to Checks
m365

Secure credential management prevents client secret usage for service principals with permanent Tier 0 roles

entra_service_principal_no_secrets_for_permanent_tier0_roles
Service: entraSeverity: critical
OfficialBuilt-inVerified
Categories:identity-accesssecrets
Resource Type:NotDefined

Microsoft Entra service principals with permanent assignments to Control Plane (Tier 0) directory roles are evaluated for the use of client secrets (password credentials) instead of more secure authentication methods such as certificates or managed identities.

Risk

A service principal authenticating with a client secret while holding a Tier 0 role creates a high-impact credential theft path. Leaked or brute-forced secrets grant immediate control-plane access, enabling tenant-wide privilege escalation, security control bypass, data exfiltration, and persistent backdoor creation—impacting confidentiality, integrity, and availability.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_service_principal_no_secrets_for_permanent_tier0_roles

Recommendation

Replace client secrets with certificates or managed identities for service principals holding Control Plane roles. Apply least privilege by removing unnecessary Tier 0 assignments. Use Privileged Identity Management (PIM) for just-in-time eligible assignments instead of permanent ones. Rotate credentials regularly and monitor sign-in logs for anomalies.

Remediation

Other

1Sign in to the Microsoft Entra admin center (https://entra.microsoft.com)
2Go to Identity > Applications > App registrations > select the application
3Under Certificates & secrets, remove all client secrets
4Under Certificates & secrets > Certificates, upload a certificate for authentication
5Alternatively, migrate to a managed identity where possible

Check Code

"""Check for service principals using client secrets with permanent Tier 0 role assignments."""

from typing import List

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 TIER_0_ROLE_TEMPLATE_IDS


class entra_service_principal_no_secrets_for_permanent_tier0_roles(Check):
    """
    Service principal with permanent Control Plane role does not use client secrets.

    This check evaluates whether service principals that hold permanent assignments
    to Tier 0 (Control Plane) directory roles authenticate using client secrets
    instead of more secure alternatives such as certificates or managed identities.

    - PASS: The service principal does not use client secrets or does not hold a
      permanent Tier 0 directory role assignment.
    - FAIL: The service principal uses client secrets and has a permanent assignment
      to at least one Tier 0 directory role.
    """

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

        Iterates over service principals and identifies those that combine password
        credentials (client secrets) with permanent Tier 0 directory role assignments.

        Returns:
            A list of reports containing the result of the check for each service principal.
        """
        findings = []

        for sp in entra_client.service_principals.values():
            report = CheckReportM365(
                metadata=self.metadata(),
                resource=sp,
                resource_name=sp.name,
                resource_id=sp.id,
            )

            active_secrets = [
                credential
                for credential in sp.password_credentials
                if credential.is_active()
            ]
            has_secrets = len(active_secrets) > 0
            tier0_roles = [
                role_id
                for role_id in sp.directory_role_template_ids
                if role_id in TIER_0_ROLE_TEMPLATE_IDS
            ]

            if has_secrets and tier0_roles:
                report.status = "FAIL"
                report.status_extended = (
                    f"Service principal '{sp.name}' uses client secrets and has "
                    f"permanent assignment to {len(tier0_roles)} Control Plane "
                    f"(Tier 0) directory role(s)."
                )
            else:
                report.status = "PASS"
                if not has_secrets and not tier0_roles:
                    report.status_extended = (
                        f"Service principal '{sp.name}' does not use client secrets "
                        f"and has no Tier 0 directory role assignments."
                    )
                elif not has_secrets:
                    report.status_extended = (
                        f"Service principal '{sp.name}' does not use client secrets."
                    )
                else:
                    report.status_extended = (
                        f"Service principal '{sp.name}' has no permanent Tier 0 "
                        f"directory role assignments."
                    )

            findings.append(report)

        return findings