From c4014fbf3d9df07ff4b56ab38e91a4ee7cc8d04a Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 13 Feb 2025 12:44:47 +0100 Subject: [PATCH 01/16] feat: add check logic and metadata --- .../__init__.py | 0 ...t_user_cannot_create_tenants.metadata.json | 30 +++++++++++++++++++ ...sure_default_user_cannot_create_tenants.py | 26 ++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/__init__.py create mode 100644 prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json create mode 100644 prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py diff --git a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/__init__.py b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json new file mode 100644 index 0000000000..887d712125 --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft365", + "CheckID": "entra_policy_ensure_default_user_cannot_create_tenants", + "CheckTitle": "Ensure that 'Restrict non-admin users from creating tenants' is set to 'Yes'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "#microsoft.graph.authorizationPolicy", + "Description": "Require administrators or appropriately delegated users to create new tenants.", + "Risk": "It is recommended to only allow an administrator to create new tenants. This prevent users from creating new Azure AD or Azure AD B2C tenants and ensures that only authorized users are able to do so.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/fundamentals/users-default-permissions", + "Remediation": { + "Code": { + "CLI": "Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{ AllowedToCreateTenants = $false }", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Navigate to Microsoft Entra admin center https://entra.microsoft.com 2. Click to expand Identity > Users > User settings 3. Set 'Restrict non-admin users from creating tenants' to 'Yes' then 'Save'", + "Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#tenant-creator" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enforcing this setting will ensure that only authorized users are able to create new tenants." +} diff --git a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py new file mode 100644 index 0000000000..b20e044da4 --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.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_policy_ensure_default_user_cannot_create_tenants(Check): + def execute(self) -> Check_Report_Microsoft365: + findings = [] + report = Check_Report_Microsoft365( + metadata=self.metadata(), resource=entra_client.authorization_policy + ) + report.status = "FAIL" + report.status_extended = "Tenant creation is not disabled for non-admin users." + + if getattr( + entra_client.authorization_policy, "default_user_role_permissions", None + ) and not getattr( + entra_client.authorization_policy.default_user_role_permissions, + "allowed_to_create_tenants", + True, + ): + report.status = "PASS" + report.status_extended = "Tenant creation is disabled for non-admin users." + + findings.append(report) + + return findings From aa99608f6ac31e732577e6f82c6f9baea0bc1992 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 13 Feb 2025 12:45:12 +0100 Subject: [PATCH 02/16] feat: add testing --- ...default_user_cannot_create_tenants_test.py | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py diff --git a/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py b/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py new file mode 100644 index 0000000000..1b84d03be9 --- /dev/null +++ b/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py @@ -0,0 +1,125 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.microsoft365.microsoft365_fixtures import ( + set_mocked_microsoft365_provider, +) + + +class Test_entra_policy_ensure_default_user_cannot_create_tenants: + def test_entra_empty_tenant(self): + entra_client = mock.MagicMock + entra_client.authorization_policy = {} + + 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_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants.entra_client", + new=entra_client, + ), + ): + from prowler.providers.microsoft365.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants import ( + entra_policy_ensure_default_user_cannot_create_tenants, + ) + + check = entra_policy_ensure_default_user_cannot_create_tenants() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_name == "" + assert result[0].resource_id == "" + assert ( + result[0].status_extended + == "Tenant creation is not disabled for non-admin users." + ) + + def test_entra_default_user_role_permissions_not_allowed_to_create_tenants(self): + id = str(uuid4()) + 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_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants.entra_client", + new=entra_client, + ), + ): + from prowler.providers.microsoft365.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants import ( + entra_policy_ensure_default_user_cannot_create_tenants, + ) + from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + DefaultUserRolePermissions, + ) + + entra_client.authorization_policy = AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=DefaultUserRolePermissions( + allowed_to_create_tenants=False + ), + guest_invite_settings="everyone", + guest_user_role_id=uuid4(), + ) + + check = entra_policy_ensure_default_user_cannot_create_tenants() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Tenant 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_tenants(self): + id = str(uuid4()) + 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_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants.entra_client", + new=entra_client, + ), + ): + from prowler.providers.microsoft365.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants import ( + entra_policy_ensure_default_user_cannot_create_tenants, + ) + from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + DefaultUserRolePermissions, + ) + + entra_client.authorization_policy = AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=DefaultUserRolePermissions( + allowed_to_create_tenants=True + ), + guest_invite_settings="everyone", + guest_user_role_id=uuid4(), + ) + + check = entra_policy_ensure_default_user_cannot_create_tenants() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Tenant creation is not disabled for non-admin users." + ) + assert result[0].resource_name == "Test" + assert result[0].resource_id == id From 2fb85f8b9f2f92e6a43e1a17097c955bd4064dd9 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 13 Feb 2025 12:50:16 +0100 Subject: [PATCH 03/16] feat: add docstrings --- ...sure_default_user_cannot_create_tenants.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py index b20e044da4..18ee5775a4 100644 --- a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py +++ b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py @@ -3,7 +3,25 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check): + """Check if default users are restricted from creating tenants. + + This check verifies whether the authorization policy prevents non-admin users + from creating new tenants in Microsoft Entra ID. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + def execute(self) -> Check_Report_Microsoft365: + """Execute the check for tenant creation restrictions. + + This method examines the authorization policy settings to determine if + non-admin users are allowed to create new tenants. If tenant creation is + restricted, the check passes. + + Returns: + List[Check_Report_Microsoft365]: A list containing the result of the check. + """ findings = [] report = Check_Report_Microsoft365( metadata=self.metadata(), resource=entra_client.authorization_policy @@ -22,5 +40,4 @@ def execute(self) -> Check_Report_Microsoft365: report.status_extended = "Tenant creation is disabled for non-admin users." findings.append(report) - return findings From 567838bb02a165d78b5e91d33341e9a422cd4b77 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 17 Feb 2025 16:52:23 +0100 Subject: [PATCH 04/16] refactor: CheckReportM365 --- prowler/lib/check/models.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 2aca0e83c1..ebe9099c1e 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -536,26 +536,34 @@ def __init__(self, metadata: Dict, resource: Any) -> None: @dataclass -class Check_Report_Microsoft365(Check_Report): +class CheckReportMicrosoft365(Check_Report): """Contains the Microsoft365 Check's finding information.""" resource_name: str resource_id: str location: str - def __init__(self, metadata: Dict, resource: Any) -> None: + def __init__( + self, + metadata: Dict, + resource: Any, + resource_name: str = "", + resource_id: str = "", + resource_location: str = "global", + ) -> None: """Initialize the Microsoft365 Check's finding information. Args: metadata: The metadata of the check. resource: Basic information about the resource. Defaults to None. + resource_name: The name of the resource related with the finding. + resource_id: The id of the resource related with the finding. + resource_location: The location of the resource related with the finding. """ super().__init__(metadata, resource) - self.resource_name = getattr( - resource, "name", getattr(resource, "resource_name", "") - ) - self.resource_id = getattr(resource, "id", getattr(resource, "resource_id", "")) - self.location = getattr(resource, "location", "global") + self.resource_name = resource_name + self.resource_id = resource_id + self.location = resource_location # Testing Pending From f343d7fa460a1166616bdf8cc8e3dfc8126f42a0 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 17 Feb 2025 16:54:42 +0100 Subject: [PATCH 05/16] fix: correct CheckReportM365 in mutelist --- prowler/providers/microsoft365/lib/mutelist/mutelist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prowler/providers/microsoft365/lib/mutelist/mutelist.py b/prowler/providers/microsoft365/lib/mutelist/mutelist.py index 83d32f4c9a..21669d9d04 100644 --- a/prowler/providers/microsoft365/lib/mutelist/mutelist.py +++ b/prowler/providers/microsoft365/lib/mutelist/mutelist.py @@ -1,4 +1,4 @@ -from prowler.lib.check.models import Check_Report_Microsoft365 +from prowler.lib.check.models import CheckReportMicrosoft365 from prowler.lib.mutelist.mutelist import Mutelist from prowler.lib.outputs.utils import unroll_dict, unroll_tags @@ -6,7 +6,7 @@ class Microsoft365Mutelist(Mutelist): def is_finding_muted( self, - finding: Check_Report_Microsoft365, + finding: CheckReportMicrosoft365, ) -> bool: return self.is_muted( finding.tenant_id, From 785b9efcc8e0b2b63db790121d0c3ebca44dfc0a Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 17 Feb 2025 16:55:50 +0100 Subject: [PATCH 06/16] refactor: resource metadata in m365 checks --- ...dmincenter_groups_not_public_visibility.py | 32 ++++++++++-- ...incenter_settings_password_never_expire.py | 33 ++++++++++-- ..._users_admins_reduced_license_footprint.py | 36 ++++++++++--- ...sers_between_two_and_four_global_admins.py | 50 +++++++++++++------ ..._thirdparty_integrated_apps_not_allowed.py | 34 ++++++++++--- 5 files changed, 147 insertions(+), 38 deletions(-) diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.py b/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.py index aea037c8a1..884d614612 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.py @@ -1,16 +1,38 @@ -from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 from prowler.providers.microsoft365.services.admincenter.admincenter_client import ( admincenter_client, ) class admincenter_groups_not_public_visibility(Check): - def execute(self) -> Check_Report_Microsoft365: + """Check if groups in Microsoft Admin Center have public visibility. + + This check verifies whether the visibility of groups in Microsoft Admin Center + is set to 'Private'. If any group has a 'Public' visibility, the check fails. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """Execute the check for groups with public visibility. + + This method iterates through all groups in Microsoft Admin Center and checks + if any group has 'Public' visibility. If so, the check fails for that group. + + Returns: + List[CheckReportMicrosoft365]: A list containing the results of the check for each group. + """ findings = [] for group in admincenter_client.groups.values(): - report = Check_Report_Microsoft365(metadata=self.metadata(), resource=group) - report.resource_id = group.id - report.resource_name = group.name + report = CheckReportMicrosoft365( + metadata=self.metadata(), + resource=group, + resource_name=group.name, + resource_id=group.id, + ) report.status = "FAIL" report.status_extended = f"Group {group.name} has {group.visibility} visibility and should be Private." 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 0bbdd3c8a1..fa1ff62265 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 @@ -1,15 +1,40 @@ -from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 from prowler.providers.microsoft365.services.admincenter.admincenter_client import ( admincenter_client, ) class admincenter_settings_password_never_expire(Check): - def execute(self) -> Check_Report_Microsoft365: + """Check if domains have a 'Password never expires' policy. + + This check verifies whether the password policy for each domain is set to never expire. + If the domain password validity period is set to `2147483647`, the policy is considered to + have 'password never expires'. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """Execute the check for password never expires policy. + + This method iterates over all domains and checks if the password validity period is set + to `2147483647`, indicating that passwords for users in the domain never expire. + + Returns: + List[CheckReportMicrosoft365]: A list of reports indicating whether the domain's password + policy is set to never expire. + """ findings = [] for domain in admincenter_client.domains.values(): - report = Check_Report_Microsoft365(self.metadata(), resource=domain) - report.resource_name = domain.id + report = CheckReportMicrosoft365( + self.metadata(), + resource=domain, + resource_name=domain.id, + resource_id=domain.id, + ) report.status = "FAIL" report.status_extended = ( f"Domain {domain.id} does not have a Password never expires policy." diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py index 0adc464c36..23ab1dc5f6 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py @@ -1,11 +1,32 @@ -from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 from prowler.providers.microsoft365.services.admincenter.admincenter_client import ( admincenter_client, ) class admincenter_users_admins_reduced_license_footprint(Check): - def execute(self) -> Check_Report_Microsoft365: + """Check if users with administrative roles have a reduced license footprint. + + This check ensures that users with administrative roles (like Global Administrator) + have valid licenses, specifically one of the allowed licenses. If a user with + administrative roles has an invalid license, the check fails. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """Execute the check for users with administrative roles and their licenses. + + This method iterates over all users and checks if those with administrative roles + have an allowed license. If a user has a valid license (AAD_PREMIUM or AAD_PREMIUM_P2), + the check passes; otherwise, it fails. + + Returns: + List[CheckReportMicrosoft365]: A list containing the result of the check for each user. + """ findings = [] allowed_licenses = ["AAD_PREMIUM", "AAD_PREMIUM_P2"] for user in admincenter_client.users.values(): @@ -13,16 +34,17 @@ def execute(self) -> Check_Report_Microsoft365: [ role for role in user.directory_roles - if "Administrator" in role or "Globar Reader" in role + if "Administrator" in role or "Global Reader" in role ] ) if admin_roles: - report = Check_Report_Microsoft365( - metadata=self.metadata(), resource=user + report = CheckReportMicrosoft365( + metadata=self.metadata(), + resource=user, + resource_name=user.name, + resource_id=user.id, ) - report.resource_id = user.id - report.resource_name = user.name report.status = "FAIL" report.status_extended = f"User {user.name} has administrative roles {admin_roles} and an invalid license {user.license if user.license else ''}." diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py index 6002e67e4c..0abebdce31 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py @@ -1,30 +1,48 @@ -from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 from prowler.providers.microsoft365.services.admincenter.admincenter_client import ( admincenter_client, ) class admincenter_users_between_two_and_four_global_admins(Check): - def execute(self) -> Check_Report_Microsoft365: - findings = [] + """Check if there are between two and four Global Administrators in Microsoft Admin Center. + + This check verifies that the number of users with the 'Global Administrator' role is + between 2 and 4, inclusive. If there are fewer than two or more than four, the check fails. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + def execute(self) -> List[CheckReportMicrosoft365]: + """Execute the check for the number of Global Administrators. + + This method checks if the number of users with the 'Global Administrator' role + is between two and four. If the condition is met, the check passes; otherwise, it fails. + + Returns: + List[CheckReportMicrosoft365]: A list containing the result of the check for the Global Administrators. + """ + findings = [] directory_roles = admincenter_client.directory_roles - report = Check_Report_Microsoft365(metadata=self.metadata(), resource={}) - report.status = "FAIL" - report.resource_name = "Global Administrator" - - if "Global Administrator" in directory_roles: - report.resource_id = getattr( - directory_roles["Global Administrator"], - "id", - "Global Administrator", + global_admin_role = directory_roles.get("Global Administrator", {}) + + if global_admin_role: + report = CheckReportMicrosoft365( + metadata=self.metadata(), + resource=global_admin_role, + resource_name=global_admin_role.name, + resource_id=global_admin_role.id, ) - - num_global_admins = len( - getattr(directory_roles["Global Administrator"], "members", []) + report.status = "FAIL" + report.status_extended = ( + "There are not between two and four global administrators." ) - if num_global_admins >= 2 and num_global_admins < 5: + num_global_admins = len(getattr(global_admin_role, "members", [])) + if 1 < num_global_admins < 5: report.status = "PASS" report.status_extended = ( f"There are {num_global_admins} global administrators." 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 index f31b8528a5..8e8df89692 100644 --- 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 @@ -1,18 +1,40 @@ -from prowler.lib.check.models import Check, Check_Report_Microsoft365 +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_thirdparty_integrated_apps_not_allowed(Check): - def execute(self) -> Check_Report_Microsoft365: - findings = [] + """Check if third-party integrated apps are not allowed for non-admin users in Entra. + + This check verifies that non-admin users are not allowed to create third-party apps. + If the policy allows app creation, the check fails. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + def execute(self) -> List[CheckReportMicrosoft365]: + """Execute the check to ensure third-party integrated apps are not allowed for non-admin users. + + This method checks if the authorization policy allows non-admin users to create apps. + If the policy allows app creation, the check fails. Otherwise, the check passes. + + Returns: + List[CheckReportMicrosoft365]: A list containing the result of the check for app creation policy. + """ + 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 = CheckReportMicrosoft365( + metadata=self.metadata(), + resource=auth_policy, + resource_name=getattr(auth_policy, "name", "Authorization Policy"), + resource_id=getattr(auth_policy, "id", "authorizationPolicy"), + ) report.status = "FAIL" report.status_extended = "App creation is not disabled for non-admin users." + # Check if the policy disables app creation 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", From 5e249b097836732f92755a3ff4f6294564c86870 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 17 Feb 2025 16:57:19 +0100 Subject: [PATCH 07/16] feat: add resource metadata to checks tests --- ...enter_groups_not_public_visibility_test.py | 10 +++ ...ter_settings_password_never_expire_test.py | 8 ++ ...s_admins_reduced_license_footprint_test.py | 14 ++++ ...between_two_and_four_global_admins_test.py | 81 +++++++++++++++++++ ...dparty_integrated_apps_not_allowed_test.py | 13 +++ 5 files changed, 126 insertions(+) diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py index 118aab01be..4a1680ce79 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py @@ -60,6 +60,11 @@ def test_admincenter_user_no_admin(self): assert len(result) == 1 assert result[0].status == "PASS" assert result[0].status_extended == "Group Group1 has Private visibility." + assert result[0].resource == { + "id": id_group1, + "name": "Group1", + "visibility": "Private", + } assert result[0].resource_name == "Group1" assert result[0].resource_id == id_group1 @@ -93,5 +98,10 @@ def test_admincenter_user_admin_compliant_license(self): assert len(result) == 1 assert result[0].status == "PASS" assert result[0].status_extended == "Group Group1 has Private visibility." + assert result[0].resource == { + "id": id_group1, + "name": "Group1", + "visibility": "Private", + } assert result[0].resource_name == "Group1" assert result[0].resource_id == id_group1 diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py index b49bc98095..70f88fd109 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py @@ -69,6 +69,10 @@ def test_admincenter_domain_password_expire(self): result[0].status_extended == f"Domain {id_domain} does not have a Password never expires policy." ) + assert result[0].resource == { + "id": id_domain, + "password_validity_period": 5, + } assert result[0].resource_name == id_domain assert result[0].resource_id == id_domain @@ -108,5 +112,9 @@ def test_admincenter_password_not_expire(self): result[0].status_extended == f"Domain {id_domain} Password policy is set to never expire." ) + assert result[0].resource == { + "id": id_domain, + "password_validity_period": 2147483647, + } assert result[0].resource_name == id_domain assert result[0].resource_id == id_domain diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py index 554cd51169..550102fdd7 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py @@ -111,6 +111,13 @@ def test_admincenter_user_admin_compliant_license(self): result[0].status_extended == "User User1 has administrative roles Global Administrator and a valid license: AAD_PREMIUM." ) + assert result[0].resource == { + "id": id_user1, + "name": "User1", + "directory_roles": ["Global Administrator"], + "license": "AAD_PREMIUM", + "user_type": None, + } assert result[0].resource_name == "User1" assert result[0].resource_id == id_user1 @@ -155,5 +162,12 @@ def test_admincenter_user_admin_non_compliant_license(self): result[0].status_extended == "User User1 has administrative roles Global Administrator and an invalid license O365 BUSINESS." ) + assert result[0].resource == { + "id": id_user1, + "name": "User1", + "directory_roles": ["Global Administrator"], + "license": "O365 BUSINESS", + "user_type": None, + } assert result[0].resource_name == "User1" assert result[0].resource_id == id_user1 diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py index 6416682e93..0493394abb 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py @@ -70,6 +70,26 @@ def test_admincenter_less_than_five_global_admins(self): assert len(result) == 1 assert result[0].status == "PASS" assert result[0].status_extended == "There are 2 global administrators." + assert result[0].resource == { + "id": id, + "name": "Global Administrator", + "members": [ + { + "id": id_user1, + "name": "User1", + "directory_roles": [], + "license": None, + "user_type": None, + }, + { + "id": id_user2, + "name": "User2", + "directory_roles": [], + "license": None, + "user_type": None, + }, + ], + } assert result[0].resource_name == "Global Administrator" assert result[0].resource_id == id @@ -124,6 +144,54 @@ def test_admincenter_more_than_five_global_admins(self): result[0].status_extended == "There are 6 global administrators. It should be more than one and less than five." ) + assert result[0].resource == { + "id": id, + "name": "Global Administrator", + "members": [ + { + "id": id_user1, + "name": "User1", + "directory_roles": [], + "license": None, + "user_type": None, + }, + { + "id": id_user2, + "name": "User2", + "directory_roles": [], + "license": None, + "user_type": None, + }, + { + "id": id_user3, + "name": "User3", + "directory_roles": [], + "license": None, + "user_type": None, + }, + { + "id": id_user4, + "name": "User4", + "directory_roles": [], + "license": None, + "user_type": None, + }, + { + "id": id_user5, + "name": "User5", + "directory_roles": [], + "license": None, + "user_type": None, + }, + { + "id": id_user6, + "name": "User6", + "directory_roles": [], + "license": None, + "user_type": None, + }, + ], + } assert result[0].resource_name == "Global Administrator" assert result[0].resource_id == id @@ -168,5 +236,18 @@ def test_admincenter_one_global_admin(self): result[0].status_extended == "There are 1 global administrators. It should be more than one and less than five." ) + assert result[0].resource == { + "id": id, + "name": "Global Administrator", + "members": [ + { + "id": id_user1, + "name": "User1", + "directory_roles": [], + "license": None, + "user_type": None, + }, + ], + } assert result[0].resource_name == "Global Administrator" assert result[0].resource_id == id 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 index 79884c0531..cdbe630902 100644 --- 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 @@ -35,6 +35,7 @@ def test_entra_no_authorization_policy(self): result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" + assert result[0].resource == {} assert result[0].resource_name == "Authorization Policy" assert result[0].resource_id == "authorizationPolicy" assert ( @@ -81,6 +82,12 @@ def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): result[0].status_extended == "App creation is disabled for non-admin users." ) + assert result[0].resource == { + "id": id, + "name": "Test", + "description": "Test", + "default_user_role_permissions": role_permissions, + } assert result[0].resource_name == "Test" assert result[0].resource_id == id @@ -123,5 +130,11 @@ def test_entra_default_user_role_permissions_allowed_to_create_apps(self): result[0].status_extended == "App creation is not disabled for non-admin users." ) + assert result[0].resource == { + "id": id, + "name": "Test", + "description": "Test", + "default_user_role_permissions": role_permissions, + } assert result[0].resource_name == "Test" assert result[0].resource_id == id From 40696c8921eddf448125a48537b2b915f4ef5911 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 18 Feb 2025 12:41:14 +0100 Subject: [PATCH 08/16] feat: add location assert in all tests --- .../admincenter_groups_not_public_visibility_test.py | 2 ++ .../admincenter_settings_password_never_expire_test.py | 2 ++ .../admincenter_users_admins_reduced_license_footprint_test.py | 2 ++ ...dmincenter_users_between_two_and_four_global_admins_test.py | 3 +++ .../entra_thirdparty_integrated_apps_not_allowed_test.py | 3 +++ 5 files changed, 12 insertions(+) diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py index 4a1680ce79..ff627dd9c0 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py @@ -67,6 +67,7 @@ def test_admincenter_user_no_admin(self): } assert result[0].resource_name == "Group1" assert result[0].resource_id == id_group1 + assert result[0].location == "global" def test_admincenter_user_admin_compliant_license(self): admincenter_client = mock.MagicMock @@ -105,3 +106,4 @@ def test_admincenter_user_admin_compliant_license(self): } assert result[0].resource_name == "Group1" assert result[0].resource_id == id_group1 + assert result[0].location == "global" diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py index 70f88fd109..ab1752f319 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py @@ -75,6 +75,7 @@ def test_admincenter_domain_password_expire(self): } assert result[0].resource_name == id_domain assert result[0].resource_id == id_domain + assert result[0].location == "global" def test_admincenter_password_not_expire(self): admincenter_client = mock.MagicMock @@ -118,3 +119,4 @@ def test_admincenter_password_not_expire(self): } assert result[0].resource_name == id_domain assert result[0].resource_id == id_domain + assert result[0].location == "global" diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py index 550102fdd7..6dba429bbc 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py @@ -120,6 +120,7 @@ def test_admincenter_user_admin_compliant_license(self): } assert result[0].resource_name == "User1" assert result[0].resource_id == id_user1 + assert result[0].location == "global" def test_admincenter_user_admin_non_compliant_license(self): admincenter_client = mock.MagicMock @@ -171,3 +172,4 @@ def test_admincenter_user_admin_non_compliant_license(self): } assert result[0].resource_name == "User1" assert result[0].resource_id == id_user1 + assert result[0].location == "global" diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py index 0493394abb..db0c6e0eec 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py @@ -92,6 +92,7 @@ def test_admincenter_less_than_five_global_admins(self): } assert result[0].resource_name == "Global Administrator" assert result[0].resource_id == id + assert result[0].location == "global" def test_admincenter_more_than_five_global_admins(self): admincenter_client = mock.MagicMock @@ -194,6 +195,7 @@ def test_admincenter_more_than_five_global_admins(self): } assert result[0].resource_name == "Global Administrator" assert result[0].resource_id == id + assert result[0].location == "global" def test_admincenter_one_global_admin(self): admincenter_client = mock.MagicMock @@ -251,3 +253,4 @@ def test_admincenter_one_global_admin(self): } assert result[0].resource_name == "Global Administrator" assert result[0].resource_id == id + assert result[0].location == "global" 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 index cdbe630902..6e9f777ab0 100644 --- 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 @@ -42,6 +42,7 @@ def test_entra_no_authorization_policy(self): result[0].status_extended == "App creation is not disabled for non-admin users." ) + assert result[0].location == "global" def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): id = str(uuid4()) @@ -90,6 +91,7 @@ def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): } assert result[0].resource_name == "Test" assert result[0].resource_id == id + assert result[0].location == "global" def test_entra_default_user_role_permissions_allowed_to_create_apps(self): id = str(uuid4()) @@ -138,3 +140,4 @@ def test_entra_default_user_role_permissions_allowed_to_create_apps(self): } assert result[0].resource_name == "Test" assert result[0].resource_id == id + assert result[0].location == "global" From 2f99dc94ea7670d8a445e20f54ffa10a24d24b00 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 18 Feb 2025 12:53:53 +0100 Subject: [PATCH 09/16] feat: remove empty defaults in CheckReportMicrosoft365 --- prowler/lib/check/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index ebe9099c1e..868b5b333a 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -547,8 +547,8 @@ def __init__( self, metadata: Dict, resource: Any, - resource_name: str = "", - resource_id: str = "", + resource_name: str, + resource_id: str, resource_location: str = "global", ) -> None: """Initialize the Microsoft365 Check's finding information. From 709662546adccd1786deef23001654c6aaf8dce5 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 18 Feb 2025 17:56:03 +0100 Subject: [PATCH 10/16] fix: adapt metadata to m365 standard --- ..._ensure_default_user_cannot_create_tenants.metadata.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json index 887d712125..12a0da5cf9 100644 --- a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json +++ b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.metadata.json @@ -15,16 +15,16 @@ "Code": { "CLI": "Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{ AllowedToCreateTenants = $false }", "NativeIaC": "", - "Other": "", + "Other": "1. Navigate to Microsoft Entra admin center https://entra.microsoft.com 2. Click to expand Identity > Users > User settings 3. Set 'Restrict non-admin users from creating tenants' to 'Yes' then 'Save'", "Terraform": "" }, "Recommendation": { - "Text": "1. Navigate to Microsoft Entra admin center https://entra.microsoft.com 2. Click to expand Identity > Users > User settings 3. Set 'Restrict non-admin users from creating tenants' to 'Yes' then 'Save'", + "Text": "Enforcing this setting will ensure that only authorized users are able to create new tenants.", "Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#tenant-creator" } }, "Categories": [], "DependsOn": [], "RelatedTo": [], - "Notes": "Enforcing this setting will ensure that only authorized users are able to create new tenants." + "Notes": "" } From 20a473c12744a152be542d913aa26545cd77a793 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Wed, 19 Feb 2025 16:48:51 +0100 Subject: [PATCH 11/16] feat: add new status extended output --- ..._thirdparty_integrated_apps_not_allowed.py | 37 ++++++++++++------- ...dparty_integrated_apps_not_allowed_test.py | 5 +-- 2 files changed, 24 insertions(+), 18 deletions(-) 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 index 8e8df89692..439fb0ba73 100644 --- 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 @@ -25,23 +25,32 @@ def execute(self) -> List[CheckReportMicrosoft365]: """ findings = [] auth_policy = entra_client.authorization_policy + report = CheckReportMicrosoft365( metadata=self.metadata(), - resource=auth_policy, - resource_name=getattr(auth_policy, "name", "Authorization Policy"), - resource_id=getattr(auth_policy, "id", "authorizationPolicy"), + resource=auth_policy if auth_policy else {}, + resource_name=auth_policy.name if auth_policy else "Authorization Policy", + resource_id=auth_policy.id if auth_policy else "authorizationPolicy", ) - report.status = "FAIL" - report.status_extended = "App creation is not disabled for non-admin users." - - # Check if the policy disables app creation 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." + + if auth_policy: + 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." + else: + report.status = "FAIL" + report.status_extended = ( + "App creation is not disabled for non-admin users." + ) + else: + report.status = "FAIL" + report.status_extended = "Authorization Policy was not found." findings.append(report) 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 index 6e9f777ab0..5eeb10fa6e 100644 --- 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 @@ -38,10 +38,7 @@ def test_entra_no_authorization_policy(self): assert result[0].resource == {} 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." - ) + assert result[0].status_extended == "Authorization Policy was not found." assert result[0].location == "global" def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): From 524c53153dc674a7301f4b132d2895d8086ffc02 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 20 Feb 2025 16:06:46 +0100 Subject: [PATCH 12/16] feat: standarize metadata --- .../admincenter_settings_password_never_expire.metadata.json | 2 +- ...enter_users_admins_reduced_license_footprint.metadata.json | 2 +- ...ter_users_between_two_and_four_global_admins.metadata.json | 2 +- ...entra_thirdparty_integrated_apps_not_allowed.metadata.json | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) 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 a1b009df6a..cfdea902fa 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 @@ -15,7 +15,7 @@ "Code": { "CLI": "Set-MsolUser -UserPrincipalName -PasswordNeverExpires $true", "NativeIaC": "", - "Other": "", + "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click to expand Settings select Org Settings. 3. Click on Security & privacy. 4. Check the Set passwords to never expire (recommended) box. 5. Click Save.", "Terraform": "" }, "Recommendation": { diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json index c141732dd4..bd9ced8b6d 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json @@ -15,7 +15,7 @@ "Code": { "CLI": "", "NativeIaC": "", - "Other": "", + "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click to expand Users select Active users. 3. Click Add a user. 4. Fill out the appropriate fields for Name, user, etc. 5. When prompted to assign licenses select as needed Microsoft Entra ID P1 or Microsoft Entra ID P2, then click Next. 6. Under the Option settings screen you may choose from several types of privileged roles. Choose Admin center access followed by the appropriate role then click Next. 7. Select Finish adding.", "Terraform": "" }, "Recommendation": { diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json index fafaf16cd1..8d5ecfb0da 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json @@ -15,7 +15,7 @@ "Code": { "CLI": "", "NativeIaC": "", - "Other": "", + "Other": "1. Navigate to the Microsoft 365 admin center https://admin.microsoft.com 2. Select Users > Active Users. 3. In the Search field enter the name of the user to be made a Global Administrator. 4. To create a new Global Admin: 1. Select the user's name. 2. A window will appear to the right. 3. Select Manage roles. 4. Select Admin center access. 5. Check Global Administrator. 6. Click Save changes. 5. To remove Global Admins: 1. Select User. 2. Under Roles select Manage roles. 3. De-Select the appropriate role. 4. Click Save changes.", "Terraform": "" }, "Recommendation": { 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 index 85e831f120..13ec515abd 100644 --- 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 @@ -15,11 +15,11 @@ "Code": { "CLI": "", "NativeIaC": "", - "Other": "", + "Other": "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", "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", + "Text": "Disable third-party integrated application permissions unless explicitly required. If third-party applications are necessary, implement strict approval processes and security controls to mitigate risks associated with external integrations.", "Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications" } }, From d78cbaf63974737954ce1a35eb8b334d6280af7e Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 20 Feb 2025 16:34:59 +0100 Subject: [PATCH 13/16] feat: standarize metadata --- .../admincenter_groups_not_public_visibility.metadata.json | 4 ++-- .../admincenter_settings_password_never_expire.metadata.json | 2 +- ...enter_users_admins_reduced_license_footprint.metadata.json | 2 +- ...ter_users_between_two_and_four_global_admins.metadata.json | 2 +- ...entra_thirdparty_integrated_apps_not_allowed.metadata.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json index 1bfc5cf677..733298af55 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json @@ -7,7 +7,7 @@ "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "high", - "ResourceType": "Microsoft365Group", + "ResourceType": "Active teams & groups", "Description": "Ensure that only organizationally managed and approved public groups exist to prevent unauthorized access to sensitive group resources like SharePoint, Teams, or other shared assets.", "Risk": "Unmanaged public groups can allow unauthorized access to organizational resources, posing a risk of data leakage or misuse through easily guessable SharePoint URLs or self-adding to groups via the Azure portal.", "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/admin/create-groups/manage-groups?view=o365-worldwide", @@ -15,7 +15,7 @@ "Code": { "CLI": "", "NativeIaC": "", - "Other": "", + "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click to expand Teams & groups select Active teams & groups. 3. On the Active teams and groups page, select the group's name that is public. 4. On the popup groups name page, select Settings. 5. Under Privacy, select Private.", "Terraform": "" }, "Recommendation": { 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 cfdea902fa..a79fc45e94 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 @@ -7,7 +7,7 @@ "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "medium", - "ResourceType": "Microsoft365Domain", + "ResourceType": "Security & privacy settings", "Description": "This control ensures that the password expiration policy is set to 'Set passwords to never expire (recommended)'. This aligns with modern recommendations to enhance security by avoiding arbitrary password changes and focusing on supplementary controls like MFA.", "Risk": "Arbitrary password expiration policies can lead to weaker passwords due to frequent changes. Users may adopt insecure habits such as using simple, memorable passwords.", "RelatedUrl": "https://www.cisecurity.org/insights/white-papers/cis-password-policy-guide", diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json index bd9ced8b6d..563dd09157 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json @@ -7,7 +7,7 @@ "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "medium", - "ResourceType": "AdministrativeAccount", + "ResourceType": "Active users", "Description": "Administrative accounts must use licenses with a reduced application footprint, such as Microsoft Entra ID P1 or P2, or avoid licenses entirely when possible. This minimizes the attack surface associated with privileged identities.", "Risk": "Licensing administrative accounts with applications like email or collaborative tools increases their exposure to social engineering attacks and malicious content, putting privileged accounts at risk.", "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/enterprise/protect-your-global-administrator-accounts?view=o365-worldwide", diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json index 8d5ecfb0da..2ffc185f91 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json @@ -7,7 +7,7 @@ "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "medium", - "ResourceType": "AdministrativeRole", + "ResourceType": "Active users", "Description": "Ensure that there are between two and four global administrators designated in your tenant. This ensures monitoring, redundancy, and reduces the risk associated with having too many privileged accounts.", "Risk": "Having only one global administrator increases the risk of unmonitored actions and operational disruptions if that administrator is unavailable. Having more than four increases the likelihood of a breach through one of these highly privileged accounts.", "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#5-limit-the-number-of-global-administrators-to-less-than-5", 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 index 13ec515abd..59676ee664 100644 --- 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 @@ -7,7 +7,7 @@ "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "high", - "ResourceType": "", + "ResourceType": "User settings", "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", From 1ade6bdbbf2ac628f3b2b6697d63cb3fdeec1277 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Fri, 21 Feb 2025 12:52:54 +0100 Subject: [PATCH 14/16] chore: enhance admincenter_users_admins_reduced_license_footprint status extended --- .../admincenter_users_admins_reduced_license_footprint.py | 2 +- .../admincenter_users_admins_reduced_license_footprint_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py index 23ab1dc5f6..aa1240cf18 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py @@ -46,7 +46,7 @@ def execute(self) -> List[CheckReportMicrosoft365]: resource_id=user.id, ) report.status = "FAIL" - report.status_extended = f"User {user.name} has administrative roles {admin_roles} and an invalid license {user.license if user.license else ''}." + report.status_extended = f"User {user.name} has administrative roles {admin_roles} and an invalid license: {user.license if user.license else None}." if user.license in allowed_licenses: report.status = "PASS" diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py index 6dba429bbc..bb57fa1c55 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py @@ -161,7 +161,7 @@ def test_admincenter_user_admin_non_compliant_license(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == "User User1 has administrative roles Global Administrator and an invalid license O365 BUSINESS." + == "User User1 has administrative roles Global Administrator and an invalid license: O365 BUSINESS." ) assert result[0].resource == { "id": id_user1, From 05cd326b27a9c6ec117a75b2f40a13f7d4fb832d Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 24 Feb 2025 12:15:45 +0100 Subject: [PATCH 15/16] feat: enhance status_extended and add test --- ..._users_admins_reduced_license_footprint.py | 14 +++-- ...s_admins_reduced_license_footprint_test.py | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py index aa1240cf18..9d57e3cbc2 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py @@ -46,11 +46,15 @@ def execute(self) -> List[CheckReportMicrosoft365]: resource_id=user.id, ) report.status = "FAIL" - report.status_extended = f"User {user.name} has administrative roles {admin_roles} and an invalid license: {user.license if user.license else None}." - - if user.license in allowed_licenses: - report.status = "PASS" - report.status_extended = f"User {user.name} has administrative roles {admin_roles} and a valid license: {user.license}." + report.status_extended = f"User {user.name} has administrative roles {admin_roles} and does not have a license." + + if user.license: + if user.license not in allowed_licenses: + report.status = "FAIL" + report.status_extended = f"User {user.name} has administrative roles {admin_roles} and an invalid license: {user.license}." + else: + report.status = "PASS" + report.status_extended = f"User {user.name} has administrative roles {admin_roles} and a valid license: {user.license}." findings.append(report) diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py index bb57fa1c55..35fff1b562 100644 --- a/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py +++ b/tests/providers/microsoft365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py @@ -173,3 +173,55 @@ def test_admincenter_user_admin_non_compliant_license(self): assert result[0].resource_name == "User1" assert result[0].resource_id == id_user1 assert result[0].location == "global" + + def test_admincenter_user_admin_no_license(self): + admincenter_client = mock.MagicMock + admincenter_client.audited_tenant = "audited_tenant" + admincenter_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.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.microsoft365.services.admincenter.admincenter_service import ( + User, + ) + from prowler.providers.microsoft365.services.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint import ( + admincenter_users_admins_reduced_license_footprint, + ) + + id_user1 = str(uuid4()) + + admincenter_client.users = { + id_user1: User( + id=id_user1, + name="User1", + directory_roles=["Global Administrator"], + license=None, + ), + } + + check = admincenter_users_admins_reduced_license_footprint() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "User User1 has administrative roles Global Administrator and does not have a license." + ) + assert result[0].resource == { + "id": id_user1, + "name": "User1", + "directory_roles": ["Global Administrator"], + "license": None, + "user_type": None, + } + assert result[0].resource_name == "User1" + assert result[0].resource_id == id_user1 + assert result[0].location == "global" From 863ac7afd3d3ab7bc3bdc04a9e1feeae23207071 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 24 Feb 2025 17:18:27 +0100 Subject: [PATCH 16/16] feat: align with provider refactor --- ...sure_default_user_cannot_create_tenants.py | 15 ++-- ...default_user_cannot_create_tenants_test.py | 68 +++++++++++++------ .../entra/microsoft365_entra_service_test.py | 48 ++++++++----- 3 files changed, 87 insertions(+), 44 deletions(-) diff --git a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py index 18ee5775a4..de2c2112eb 100644 --- a/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py +++ b/prowler/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py @@ -1,4 +1,6 @@ -from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 from prowler.providers.microsoft365.services.entra.entra_client import entra_client @@ -12,7 +14,7 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check): metadata: Metadata associated with the check (inherited from Check). """ - def execute(self) -> Check_Report_Microsoft365: + def execute(self) -> List[CheckReportMicrosoft365]: """Execute the check for tenant creation restrictions. This method examines the authorization policy settings to determine if @@ -23,8 +25,13 @@ def execute(self) -> Check_Report_Microsoft365: List[Check_Report_Microsoft365]: A list containing the result of the check. """ findings = [] - report = Check_Report_Microsoft365( - metadata=self.metadata(), resource=entra_client.authorization_policy + auth_policy = entra_client.authorization_policy + + report = CheckReportMicrosoft365( + metadata=self.metadata(), + resource=auth_policy if auth_policy else {}, + resource_name=auth_policy.name if auth_policy else "Authorization Policy", + resource_id=auth_policy.id if auth_policy else "authorizationPolicy", ) report.status = "FAIL" report.status_extended = "Tenant creation is not disabled for non-admin users." diff --git a/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py b/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py index 1b84d03be9..5adf54c01e 100644 --- a/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py +++ b/tests/providers/microsoft365/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/microsoft365_entra_policy_ensure_default_user_cannot_create_tenants_test.py @@ -1,6 +1,10 @@ from unittest import mock from uuid import uuid4 +from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + DefaultUserRolePermissions, +) from tests.providers.microsoft365.microsoft365_fixtures import ( set_mocked_microsoft365_provider, ) @@ -29,14 +33,16 @@ def test_entra_empty_tenant(self): result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" - assert result[0].resource_name == "" - assert result[0].resource_id == "" assert ( result[0].status_extended == "Tenant creation is not disabled for non-admin users." ) + assert result[0].resource == {} + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert result[0].location == "global" - def test_entra_default_user_role_permissions_not_allowed_to_create_tenants(self): + def test_entra_default_user_role_permissions_allowed_to_create_tenants(self): id = str(uuid4()) entra_client = mock.MagicMock @@ -53,34 +59,43 @@ def test_entra_default_user_role_permissions_not_allowed_to_create_tenants(self) from prowler.providers.microsoft365.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants import ( entra_policy_ensure_default_user_cannot_create_tenants, ) - from prowler.providers.microsoft365.services.entra.entra_service import ( - AuthorizationPolicy, - DefaultUserRolePermissions, - ) entra_client.authorization_policy = AuthorizationPolicy( id=id, name="Test", description="Test", default_user_role_permissions=DefaultUserRolePermissions( - allowed_to_create_tenants=False + allowed_to_create_tenants=True ), - guest_invite_settings="everyone", - guest_user_role_id=uuid4(), ) check = entra_policy_ensure_default_user_cannot_create_tenants() result = check.execute() assert len(result) == 1 - assert result[0].status == "PASS" + assert result[0].status == "FAIL" assert ( result[0].status_extended - == "Tenant creation is disabled for non-admin users." + == "Tenant creation is not disabled for non-admin users." ) + assert result[0].resource == { + "id": id, + "name": "Test", + "description": "Test", + "default_user_role_permissions": { + "allowed_to_create_apps": None, + "allowed_to_create_security_groups": None, + "allowed_to_create_tenants": True, + "allowed_to_read_bitlocker_keys_for_owned_device": None, + "allowed_to_read_other_users": None, + "odata_type": None, + "permission_grant_policies_assigned": None, + }, + } assert result[0].resource_name == "Test" assert result[0].resource_id == id + assert result[0].location == "global" - def test_entra_default_user_role_permissions_allowed_to_create_tenants(self): + def test_entra_default_user_role_permissions_not_allowed_to_create_tenants(self): id = str(uuid4()) entra_client = mock.MagicMock @@ -97,29 +112,38 @@ def test_entra_default_user_role_permissions_allowed_to_create_tenants(self): from prowler.providers.microsoft365.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants import ( entra_policy_ensure_default_user_cannot_create_tenants, ) - from prowler.providers.microsoft365.services.entra.entra_service import ( - AuthorizationPolicy, - DefaultUserRolePermissions, - ) entra_client.authorization_policy = AuthorizationPolicy( id=id, name="Test", description="Test", default_user_role_permissions=DefaultUserRolePermissions( - allowed_to_create_tenants=True + allowed_to_create_tenants=False ), - guest_invite_settings="everyone", - guest_user_role_id=uuid4(), ) check = entra_policy_ensure_default_user_cannot_create_tenants() result = check.execute() assert len(result) == 1 - assert result[0].status == "FAIL" + assert result[0].status == "PASS" assert ( result[0].status_extended - == "Tenant creation is not disabled for non-admin users." + == "Tenant creation is disabled for non-admin users." ) + assert result[0].resource == { + "id": id, + "name": "Test", + "description": "Test", + "default_user_role_permissions": { + "allowed_to_create_apps": None, + "allowed_to_create_security_groups": None, + "allowed_to_create_tenants": False, + "allowed_to_read_bitlocker_keys_for_owned_device": None, + "allowed_to_read_other_users": None, + "odata_type": None, + "permission_grant_policies_assigned": None, + }, + } assert result[0].resource_name == "Test" assert result[0].resource_id == id + assert result[0].location == "global" diff --git a/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py b/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py index 86109478e2..caf9fdec74 100644 --- a/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py +++ b/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py @@ -3,6 +3,7 @@ from prowler.providers.microsoft365.models import Microsoft365IdentityInfo from prowler.providers.microsoft365.services.entra.entra_service import ( AuthorizationPolicy, + DefaultUserRolePermissions, Entra, ) from tests.providers.microsoft365.microsoft365_fixtures import ( @@ -12,20 +13,20 @@ async def mock_entra_get_authorization_policy(_): - return { - "id-1": AuthorizationPolicy( - id="id-1", - name="Name 1", - description="Description 1", - default_user_role_permissions=None, - ) - } + return AuthorizationPolicy( + id="id-1", + name="Name 1", + description="Description 1", + default_user_role_permissions=DefaultUserRolePermissions( + allowed_to_create_apps=True, + allowed_to_create_security_groups=True, + allowed_to_create_tenants=True, + allowed_to_read_bitlocker_keys_for_owned_device=True, + allowed_to_read_other_users=True, + ), + ) -@patch( - "prowler.providers.microsoft365.services.entra.entra_service.Entra._get_authorization_policy", - new=mock_entra_get_authorization_policy, -) class Test_Entra_Service: def test_get_client(self): admincenter_client = Entra( @@ -35,11 +36,22 @@ def test_get_client(self): ) assert admincenter_client.client.__class__.__name__ == "GraphServiceClient" + @patch( + "prowler.providers.microsoft365.services.entra.entra_service.Entra._get_authorization_policy", + new=mock_entra_get_authorization_policy, + ) def test_get_authorization_policy(self): entra_client = Entra(set_mocked_microsoft365_provider()) - assert entra_client.authorization_policy["id-1"].id == "id-1" - assert entra_client.authorization_policy["id-1"].name == "Name 1" - assert entra_client.authorization_policy["id-1"].description == "Description 1" - assert not entra_client.authorization_policy[ - "id-1" - ].default_user_role_permissions + assert entra_client.authorization_policy.id == "id-1" + assert entra_client.authorization_policy.name == "Name 1" + assert entra_client.authorization_policy.description == "Description 1" + assert ( + entra_client.authorization_policy.default_user_role_permissions + == DefaultUserRolePermissions( + allowed_to_create_apps=True, + allowed_to_create_security_groups=True, + allowed_to_create_tenants=True, + allowed_to_read_bitlocker_keys_for_owned_device=True, + allowed_to_read_other_users=True, + ) + )