Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(entra): add new check entra_admin_consent_workflow_enabled #7110

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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": ""
}
Original file line number Diff line number Diff line change
@@ -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
administrator’s 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
29 changes: 28 additions & 1 deletion prowler/providers/microsoft365/services/entra/entra_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
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...")
Expand Down Expand Up @@ -83,6 +85,24 @@

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(

Check warning on line 93 in prowler/providers/microsoft365/services/entra/entra_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/microsoft365/services/entra/entra_service.py#L93

Added line #L93 was not covered by tests
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]
Expand All @@ -99,3 +119,10 @@
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
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 == {
"admin_consent_enabled": True,
"notify_reviewers": True,
"email_reminders_to_reviewers": False,
"duration_in_days": 30,
}

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 == {
"admin_consent_enabled": True,
"notify_reviewers": False,
"email_reminders_to_reviewers": False,
"duration_in_days": 30,
}

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 == {
"admin_consent_enabled": False,
"notify_reviewers": True,
"email_reminders_to_reviewers": False,
"duration_in_days": 30,
}

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 == []
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from prowler.providers.microsoft365.models import Microsoft365IdentityInfo
from prowler.providers.microsoft365.services.entra.entra_service import (
AdminConsentPolicy,
AuthorizationPolicy,
DefaultUserRolePermissions,
Entra,
Expand All @@ -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(
Expand Down Expand Up @@ -55,3 +65,14 @@ def test_get_authorization_policy(self):
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