feat(entra): add new check entra_admin_consent_workflow_enabled (#7110)

This commit is contained in:
Daniel Barranquero
2025-03-11 13:18:17 +01:00
committed by GitHub
parent 0e46be54ec
commit 8a76fea310
6 changed files with 285 additions and 1 deletions
@@ -0,0 +1,30 @@
{
"Provider": "microsoft365",
"CheckID": "entra_admin_consent_workflow_enabled",
"CheckTitle": "Ensure the admin consent workflow is enabled.",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Organization Settings",
"Description": "Ensure that the admin consent workflow is enabled in Microsoft Entra to allow users to request admin approval for applications requiring consent.",
"Risk": "If the admin consent workflow is not enabled, users may be blocked from accessing applications that require admin consent, leading to potential work disruptions or unauthorized workarounds.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft Entra admin center https://entra.microsoft.com/. 2. Click to expand Identity > Applications and select Enterprise applications. 3. Under Security, select Consent and permissions. 4. Under Manage, select Admin consent settings. 5. Set 'Users can request admin consent to apps they are unable to consent to' to 'Yes'. 6. Configure the reviewers and email notifications settings. 7. Click Save.",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable the admin consent workflow in Microsoft Entra to securely manage application consent requests.",
"Url": "https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportMicrosoft365
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
class entra_admin_consent_workflow_enabled(Check):
"""
Ensure the admin consent workflow is enabled in Microsoft Entra.
This check verifies that the admin consent workflow is enabled in Microsoft Entra to allow users
to request admin approval for applications requiring consent. Enabling the admin consent workflow
ensures that applications which require additional permissions are only granted access after an
administrators approval, reducing the risk of unauthorized access and work disruptions.
The check fails if the admin consent workflow is not enabled, indicating that users might be blocked
from accessing critical applications or forced to use insecure workarounds.
"""
def execute(self) -> List[CheckReportMicrosoft365]:
"""
Execute the admin consent workflow requirement check.
Retrieves the admin consent policy from the Microsoft Entra client and generates a report indicating
whether the admin consent workflow is enabled.
Returns:
List[CheckReportMicrosoft365]: A list containing the report with the result of the check.
"""
findings = []
admin_consent_policy = entra_client.admin_consent_policy
if admin_consent_policy:
report = CheckReportMicrosoft365(
self.metadata(),
resource=admin_consent_policy,
resource_name="Admin Consent Policy",
resource_id=entra_client.tenant_domain,
)
report.status = "FAIL"
report.status_extended = "The admin consent workflow is not enabled in Microsoft Entra; users may be blocked from accessing applications that require admin consent."
if admin_consent_policy.admin_consent_enabled:
report.status = "PASS"
report.status_extended = "The admin consent workflow is enabled in Microsoft Entra, allowing users to request admin approval for applications."
if admin_consent_policy.notify_reviewers:
report.status_extended += " Reviewers will be notified."
else:
report.status_extended += (
" Reviewers will not be notified, we recommend notifying them."
)
findings.append(report)
return findings
@@ -13,14 +13,16 @@ class Entra(Microsoft365Service):
super().__init__(provider)
loop = get_event_loop()
self.tenant_domain = provider.identity.tenant_domain
attributes = loop.run_until_complete(
gather(
self._get_authorization_policy(),
self._get_admin_consent_policy(),
)
)
self.authorization_policy = attributes[0]
self.admin_consent_policy = attributes[1]
async def _get_authorization_policy(self):
logger.info("Entra - Getting authorization policy...")
@@ -83,6 +85,24 @@ class Entra(Microsoft365Service):
return authorization_policy
async def _get_admin_consent_policy(self):
logger.info("Entra - Getting group settings...")
admin_consent_policy = None
try:
policy = await self.client.policies.admin_consent_request_policy.get()
admin_consent_policy = AdminConsentPolicy(
admin_consent_enabled=policy.is_enabled,
notify_reviewers=policy.notify_reviewers,
email_reminders_to_reviewers=policy.reminders_enabled,
duration_in_days=policy.request_duration_in_days,
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return admin_consent_policy
class DefaultUserRolePermissions(BaseModel):
allowed_to_create_apps: Optional[bool]
@@ -99,3 +119,10 @@ class AuthorizationPolicy(BaseModel):
name: str
description: str
default_user_role_permissions: Optional[DefaultUserRolePermissions]
class AdminConsentPolicy(BaseModel):
admin_consent_enabled: bool
notify_reviewers: bool
email_reminders_to_reviewers: bool
duration_in_days: int
@@ -0,0 +1,154 @@
from unittest import mock
from prowler.providers.microsoft365.services.entra.entra_service import (
AdminConsentPolicy,
)
from tests.providers.microsoft365.microsoft365_fixtures import (
DOMAIN,
set_mocked_microsoft365_provider,
)
class Test_entra_admin_consent_workflow_enabled:
def test_admin_consent_enabled(self):
"""
Test when admin_consent_enabled is True:
The check should PASS because the admin consent workflow is enabled.
"""
entra_client = mock.MagicMock()
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_microsoft365_provider(),
), mock.patch(
"prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled.entra_client",
new=entra_client,
):
from prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled import (
entra_admin_consent_workflow_enabled,
)
entra_client.admin_consent_policy = AdminConsentPolicy(
admin_consent_enabled=True,
notify_reviewers=True,
email_reminders_to_reviewers=False,
duration_in_days=30,
)
entra_client.tenant_domain = DOMAIN
check = entra_admin_consent_workflow_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"The admin consent workflow is enabled in Microsoft Entra, allowing users to request admin approval for applications. Reviewers will be notified."
)
assert result[0].resource_id == DOMAIN
assert result[0].location == "global"
assert result[0].resource_name == "Admin Consent Policy"
assert result[0].resource == entra_client.admin_consent_policy.dict()
def test_admin_consent_enabled_without_notifications(self):
"""
Test when admin_consent_enabled is True:
The check should PASS because the admin consent workflow is enabled.
"""
entra_client = mock.MagicMock()
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_microsoft365_provider(),
), mock.patch(
"prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled.entra_client",
new=entra_client,
):
from prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled import (
entra_admin_consent_workflow_enabled,
)
entra_client.admin_consent_policy = AdminConsentPolicy(
admin_consent_enabled=True,
notify_reviewers=False,
email_reminders_to_reviewers=False,
duration_in_days=30,
)
entra_client.tenant_domain = DOMAIN
check = entra_admin_consent_workflow_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"The admin consent workflow is enabled in Microsoft Entra, allowing users to request admin approval for applications. Reviewers will not be notified, we recommend notifying them."
)
assert result[0].resource_id == DOMAIN
assert result[0].location == "global"
assert result[0].resource_name == "Admin Consent Policy"
assert result[0].resource == entra_client.admin_consent_policy.dict()
def test_admin_consent_disabled(self):
"""
Test when admin_consent_enabled is False:
The check should FAIL because the admin consent workflow is not enabled.
"""
entra_client = mock.MagicMock()
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_microsoft365_provider(),
), mock.patch(
"prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled.entra_client",
new=entra_client,
):
from prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled import (
entra_admin_consent_workflow_enabled,
)
entra_client.admin_consent_policy = AdminConsentPolicy(
admin_consent_enabled=False,
notify_reviewers=True,
email_reminders_to_reviewers=False,
duration_in_days=30,
)
entra_client.tenant_domain = DOMAIN
check = entra_admin_consent_workflow_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"The admin consent workflow is not enabled in Microsoft Entra; users may be blocked from accessing applications that require admin consent."
)
assert result[0].resource_id == DOMAIN
assert result[0].location == "global"
assert result[0].resource_name == "Admin Consent Policy"
assert result[0].resource == entra_client.admin_consent_policy.dict()
def test_no_policy(self):
"""
Test when entra_client.admin_consent_policy is None:
The check should return an empty list of findings.
"""
entra_client = mock.MagicMock()
entra_client.admin_consent_policy = None
entra_client.tenant_domain = DOMAIN
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_microsoft365_provider(),
), mock.patch(
"prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled.entra_client",
new=entra_client,
):
from prowler.providers.microsoft365.services.entra.entra_admin_consent_workflow_enabled.entra_admin_consent_workflow_enabled import (
entra_admin_consent_workflow_enabled,
)
check = entra_admin_consent_workflow_enabled()
result = check.execute()
assert len(result) == 0
assert result == []
@@ -2,6 +2,7 @@ from unittest.mock import patch
from prowler.providers.microsoft365.models import Microsoft365IdentityInfo
from prowler.providers.microsoft365.services.entra.entra_service import (
AdminConsentPolicy,
AuthorizationPolicy,
DefaultUserRolePermissions,
Entra,
@@ -27,6 +28,15 @@ async def mock_entra_get_authorization_policy(_):
)
async def mock_entra_get_admin_consent_policy(_):
return AdminConsentPolicy(
admin_consent_enabled=True,
notify_reviewers=True,
email_reminders_to_reviewers=False,
duration_in_days=30,
)
class Test_Entra_Service:
def test_get_client(self):
admincenter_client = Entra(
@@ -55,3 +65,14 @@ class Test_Entra_Service:
allowed_to_read_other_users=True,
)
)
@patch(
"prowler.providers.microsoft365.services.entra.entra_service.Entra._get_admin_consent_policy",
new=mock_entra_get_admin_consent_policy,
)
def test_get_admin_consent_policy(self):
entra_client = Entra(set_mocked_microsoft365_provider())
assert entra_client.admin_consent_policy.admin_consent_enabled
assert entra_client.admin_consent_policy.notify_reviewers
assert entra_client.admin_consent_policy.email_reminders_to_reviewers is False
assert entra_client.admin_consent_policy.duration_in_days == 30