From b3aa8000827daaf3c20357a8f0198e1bb8bc6f72 Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:45:48 +0100 Subject: [PATCH] feat(entra): add new check `entra_thirdparty_integrated_apps_not_allowed` (#6357) Co-authored-by: MrCloudSec --- prowler/lib/check/models.py | 2 - ...ttings_password_never_expire.metadata.json | 2 +- ...incenter_settings_password_never_expire.py | 1 - .../services/entra/entra_service.py | 90 ++++++------- .../__init__.py | 0 ..._integrated_apps_not_allowed.metadata.json | 30 +++++ ..._thirdparty_integrated_apps_not_allowed.py | 26 ++++ ...dparty_integrated_apps_not_allowed_test.py | 127 ++++++++++++++++++ 8 files changed, 227 insertions(+), 51 deletions(-) create mode 100644 prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/__init__.py create mode 100644 prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.metadata.json create mode 100644 prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py create mode 100644 tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 928d68f075..2aca0e83c1 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -541,7 +541,6 @@ class Check_Report_Microsoft365(Check_Report): resource_name: str resource_id: str - tenant_id: str location: str def __init__(self, metadata: Dict, resource: Any) -> None: @@ -556,7 +555,6 @@ class Check_Report_Microsoft365(Check_Report): resource, "name", getattr(resource, "resource_name", "") ) self.resource_id = getattr(resource, "id", getattr(resource, "resource_id", "")) - self.tenant_id = getattr(resource, "tenant_id", "") self.location = getattr(resource, "location", "global") diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json index 13528b0008..a1b009df6a 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json @@ -1,5 +1,5 @@ { - "Provider": "microsoft", + "Provider": "microsoft365", "CheckID": "admincenter_settings_password_never_expire", "CheckTitle": "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)'", "CheckType": [], diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py index 8de62dd8d5..0bbdd3c8a1 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py @@ -9,7 +9,6 @@ class admincenter_settings_password_never_expire(Check): findings = [] for domain in admincenter_client.domains.values(): report = Check_Report_Microsoft365(self.metadata(), resource=domain) - report.resource_id = domain.id report.resource_name = domain.id report.status = "FAIL" report.status_extended = ( diff --git a/prowler/providers/microsoft365/services/entra/entra_service.py b/prowler/providers/microsoft365/services/entra/entra_service.py index 8561a31edd..14d04ad93a 100644 --- a/prowler/providers/microsoft365/services/entra/entra_service.py +++ b/prowler/providers/microsoft365/services/entra/entra_service.py @@ -25,7 +25,7 @@ class Entra(Microsoft365Service): async def _get_authorization_policy(self): logger.info("Entra - Getting authorization policy...") - authorization_policy = {} + authorization_policy = None try: auth_policy = await self.client.policies.authorization_policy.get() @@ -33,52 +33,48 @@ class Entra(Microsoft365Service): auth_policy, "default_user_role_permissions", None ) - authorization_policy.update( - { - auth_policy.id: AuthorizationPolicy( - id=auth_policy.id, - name=auth_policy.display_name, - description=auth_policy.description, - default_user_role_permissions=DefaultUserRolePermissions( - allowed_to_create_apps=getattr( - default_user_role_permissions, - "allowed_to_create_apps", - None, - ), - allowed_to_create_security_groups=getattr( - default_user_role_permissions, - "allowed_to_create_security_groups", - None, - ), - allowed_to_create_tenants=getattr( - default_user_role_permissions, - "allowed_to_create_tenants", - None, - ), - allowed_to_read_bitlocker_keys_for_owned_device=getattr( - default_user_role_permissions, - "allowed_to_read_bitlocker_keys_for_owned_device", - None, - ), - allowed_to_read_other_users=getattr( - default_user_role_permissions, - "allowed_to_read_other_users", - None, - ), - odata_type=getattr( - default_user_role_permissions, "odata_type", None - ), - permission_grant_policies_assigned=[ - policy_assigned - for policy_assigned in getattr( - default_user_role_permissions, - "permission_grant_policies_assigned", - [], - ) - ], - ), - ) - } + authorization_policy = AuthorizationPolicy( + id=auth_policy.id, + name=auth_policy.display_name, + description=auth_policy.description, + default_user_role_permissions=DefaultUserRolePermissions( + allowed_to_create_apps=getattr( + default_user_role_permissions, + "allowed_to_create_apps", + None, + ), + allowed_to_create_security_groups=getattr( + default_user_role_permissions, + "allowed_to_create_security_groups", + None, + ), + allowed_to_create_tenants=getattr( + default_user_role_permissions, + "allowed_to_create_tenants", + None, + ), + allowed_to_read_bitlocker_keys_for_owned_device=getattr( + default_user_role_permissions, + "allowed_to_read_bitlocker_keys_for_owned_device", + None, + ), + allowed_to_read_other_users=getattr( + default_user_role_permissions, + "allowed_to_read_other_users", + None, + ), + odata_type=getattr( + default_user_role_permissions, "odata_type", None + ), + permission_grant_policies_assigned=[ + policy_assigned + for policy_assigned in getattr( + default_user_role_permissions, + "permission_grant_policies_assigned", + [], + ) + ], + ), ) except Exception as error: logger.error( diff --git a/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/__init__.py b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.metadata.json b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.metadata.json new file mode 100644 index 0000000000..85e831f120 --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft365", + "CheckID": "entra_thirdparty_integrated_apps_not_allowed", + "CheckTitle": "Ensure third party integrated applications are not allowed", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "", + "Description": "Require administrators or appropriately delegated users to register third-party applications.", + "Risk": "It is recommended to only allow an administrator to register custom-developed applications. This ensures that the application undergoes a formal security review and approval process prior to exposing Azure Active Directory data. Certain users like developers or other high-request users may also be delegated permissions to prevent them from waiting on an administrative user. Your organization should review your policies and decide your needs.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity-platform/how-applications-are-added#who-has-permission-to-add-applications-to-my-microsoft-entra-instance", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Entra select the Portal Menu 2. Select Azure Active Directory 3. Select Users 4. Select User settings 5. Ensure that Users can register applications is set to No", + "Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enforcing this setting will create additional requests for approval that will need to be addressed by an administrator. If permissions are delegated, a user may approve a malevolent third party application, potentially giving it access to your data." +} diff --git a/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py new file mode 100644 index 0000000000..f31b8528a5 --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from prowler.providers.microsoft365.services.entra.entra_client import entra_client + + +class entra_thirdparty_integrated_apps_not_allowed(Check): + def execute(self) -> Check_Report_Microsoft365: + findings = [] + + auth_policy = entra_client.authorization_policy + report = Check_Report_Microsoft365(self.metadata(), auth_policy) + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") + report.status = "FAIL" + report.status_extended = "App creation is not disabled for non-admin users." + + if getattr(auth_policy, "default_user_role_permissions", None) and not getattr( + auth_policy.default_user_role_permissions, + "allowed_to_create_apps", + True, + ): + report.status = "PASS" + report.status_extended = "App creation is disabled for non-admin users." + + findings.append(report) + + return findings diff --git a/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py b/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py new file mode 100644 index 0000000000..79884c0531 --- /dev/null +++ b/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py @@ -0,0 +1,127 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.microsoft365.services.entra.entra_service import ( + DefaultUserRolePermissions, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +class Test_entra_thirdparty_integrated_apps_not_allowed: + def test_entra_no_authorization_policy(self): + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_client.audited_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_thirdparty_integrated_apps_not_allowed.entra_thirdparty_integrated_apps_not_allowed.entra_client", + new=entra_client, + ), + ): + from prowler.providers.microsoft365.services.entra.entra_thirdparty_integrated_apps_not_allowed.entra_thirdparty_integrated_apps_not_allowed import ( + entra_thirdparty_integrated_apps_not_allowed, + ) + + entra_client.authorization_policy = None + + check = entra_thirdparty_integrated_apps_not_allowed() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert ( + result[0].status_extended + == "App creation is not disabled for non-admin users." + ) + + def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): + id = str(uuid4()) + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_client.audited_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_thirdparty_integrated_apps_not_allowed.entra_thirdparty_integrated_apps_not_allowed.entra_client", + new=entra_client, + ), + ): + from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + ) + from prowler.providers.microsoft365.services.entra.entra_thirdparty_integrated_apps_not_allowed.entra_thirdparty_integrated_apps_not_allowed import ( + entra_thirdparty_integrated_apps_not_allowed, + ) + + role_permissions = DefaultUserRolePermissions(allowed_to_create_apps=False) + entra_client.authorization_policy = AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=role_permissions, + ) + + check = entra_thirdparty_integrated_apps_not_allowed() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "App creation is disabled for non-admin users." + ) + assert result[0].resource_name == "Test" + assert result[0].resource_id == id + + def test_entra_default_user_role_permissions_allowed_to_create_apps(self): + id = str(uuid4()) + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_client.audited_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_thirdparty_integrated_apps_not_allowed.entra_thirdparty_integrated_apps_not_allowed.entra_client", + new=entra_client, + ), + ): + from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + ) + from prowler.providers.microsoft365.services.entra.entra_thirdparty_integrated_apps_not_allowed.entra_thirdparty_integrated_apps_not_allowed import ( + entra_thirdparty_integrated_apps_not_allowed, + ) + + role_permissions = DefaultUserRolePermissions(allowed_to_create_apps=True) + entra_client.authorization_policy = AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=role_permissions, + ) + + check = entra_thirdparty_integrated_apps_not_allowed() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "App creation is not disabled for non-admin users." + ) + assert result[0].resource_name == "Test" + assert result[0].resource_id == id