diff --git a/prowler/providers/azure/lib/service/service.py b/prowler/providers/azure/lib/service/service.py index cd00cb95e2..d391aa7618 100644 --- a/prowler/providers/azure/lib/service/service.py +++ b/prowler/providers/azure/lib/service/service.py @@ -1,3 +1,5 @@ +from typing import Any + from prowler.lib.logger import logger from prowler.providers.azure.lib.audit_info.models import Azure_Audit_Info @@ -5,7 +7,7 @@ from prowler.providers.azure.lib.audit_info.models import Azure_Audit_Info class AzureService: def __init__( self, - service: str, + service: Any, audit_info: Azure_Audit_Info, ): self.clients = self.__set_clients__( diff --git a/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/__init__.py b/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups.metadata.json b/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups.metadata.json new file mode 100644 index 0000000000..23047f5663 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_policy_default_users_cannot_create_security_groups", + "CheckTitle": "Ensure that 'Users can create security groups in Azure portals, API or PowerShell' is set to 'No'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "#microsoft.graph.authorizationPolicy", + "Description": "Restrict security group creation to administrators only.", + "Risk": "When creating security groups is enabled, all users in the directory are allowed to create new security groups and add members to those groups. Unless a business requires this day-to-day delegation, security group creation should be restricted to administrators only.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/users/groups-self-service-management", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/users-can-create-security-groups.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Select Groups 4. Select General under Settings 5. Set Users can create security groups in Azure portals, API or PowerShell to No", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enabling this setting could create a number of requests that would need to be managed by an administrator." +} diff --git a/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups.py b/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups.py new file mode 100644 index 0000000000..c232d8f5d6 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_policy_default_users_cannot_create_security_groups(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant_domain, auth_policy in entra_client.authorization_policy.items(): + + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") + report.status_extended = "Non-privileged users are able to create security groups via the Access Panel and the Azure administration portal." + + if getattr( + auth_policy, "default_user_role_permissions", None + ) and not getattr( + auth_policy.default_user_role_permissions, + "allowed_to_create_security_groups", + True, + ): + report.status = "PASS" + report.status_extended = "Non-privileged users are not able to create security groups via the Access Panel and the Azure administration portal." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.metadata.json b/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.metadata.json index fae3e23e47..0cc445a713 100644 --- a/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.metadata.json +++ b/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.metadata.json @@ -15,7 +15,7 @@ "Code": { "CLI": "", "NativeIaC": "", - "Other": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/users-can-register-applications.html", "Terraform": "" }, "Recommendation": { diff --git a/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.py b/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.py index 7622b7e2ec..7d910bd28d 100644 --- a/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.py +++ b/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps.py @@ -10,12 +10,14 @@ class entra_policy_ensure_default_user_cannot_create_apps(Check): report = Check_Report_Azure(self.metadata()) report.status = "FAIL" - report.subscription = f"All from tenant '{tenant_domain}'" - report.resource_name = auth_policy.name - report.resource_id = auth_policy.id + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") report.status_extended = "App creation is not disabled for non-admin users." - if auth_policy.default_user_role_permissions and not getattr( + if getattr( + auth_policy, "default_user_role_permissions", None + ) and not getattr( auth_policy.default_user_role_permissions, "allowed_to_create_apps", True, diff --git a/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py b/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py index 4803d46bee..f8320ef24a 100644 --- a/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py +++ b/prowler/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants.py @@ -7,17 +7,18 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check): findings = [] for tenant_domain, auth_policy in entra_client.authorization_policy.items(): - report = Check_Report_Azure(self.metadata()) report.status = "FAIL" - report.subscription = f"All from tenant '{tenant_domain}'" - report.resource_name = auth_policy.name - report.resource_id = auth_policy.id + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") report.status_extended = ( "Tenants creation is not disabled for non-admin users." ) - if auth_policy.default_user_role_permissions and not getattr( + if getattr( + auth_policy, "default_user_role_permissions", None + ) and not getattr( auth_policy.default_user_role_permissions, "allowed_to_create_tenants", True, diff --git a/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/__init__.py b/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles.metadata.json b/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles.metadata.json new file mode 100644 index 0000000000..7e91ea70c2 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_policy_guest_invite_only_for_admin_roles", + "CheckTitle": "Ensure that 'Guest invite restrictions' is set to 'Only users assigned to specific admin roles can invite guest users'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "#microsoft.graph.authorizationPolicy", + "Description": "Restrict invitations to users with specific administrative roles only.", + "Risk": "Restricting invitations to users with specific administrator roles ensures that only authorized accounts have access to cloud resources. This helps to maintain 'Need to Know' permissions and prevents inadvertent access to data. By default the setting Guest invite restrictions is set to Anyone in the organization can invite guest users including guests and non-admins. This would allow anyone within the organization to invite guests and non-admins to the tenant, posing a security risk.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/external-id/external-collaboration-settings-configure", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Then External Identities 4. Select External collaboration settings 5. Under Guest invite settings, for Guest invite restrictions, ensure that Only users assigned to specific admin roles can invite guest users is selected", + "Url": "https://learn.microsoft.com/en-us/answers/questions/685101/how-to-allow-only-admins-to-add-guests" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "With the option of Only users assigned to specific admin roles can invite guest users selected, users with specific admin roles will be in charge of sending invitations to the external users, requiring additional overhead by them to manage user accounts. This will mean coordinating with other departments as they are onboarding new users." +} diff --git a/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles.py b/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles.py new file mode 100644 index 0000000000..edc0303fe4 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_policy_guest_invite_only_for_admin_roles(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant_domain, auth_policy in entra_client.authorization_policy.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") + report.status_extended = "Guest invitations are not restricted to users with specific administrative roles only." + + if ( + getattr(auth_policy, "guest_invite_settings", "everyone") + == "adminsAndGuestInviters" + or getattr(auth_policy, "guest_invite_settings", "everyone") == "none" + ): + report.status = "PASS" + report.status_extended = "Guest invitations are restricted to users with specific administrative roles only." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/__init__.py b/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions.metadata.json b/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions.metadata.json new file mode 100644 index 0000000000..2fbe16eb02 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_policy_guest_users_access_restrictions", + "CheckTitle": "Ensure That 'Guest users access restrictions' is set to 'Guest user access is restricted to properties and memberships of their own directory objects'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "#microsoft.graph.authorizationPolicy", + "Description": "Limit guest user permissions.", + "Risk": "Limiting guest access ensures that guest accounts do not have permission for certain directory tasks, such as enumerating users, groups or other directory resources, and cannot be assigned to administrative roles in your directory. Guest access has three levels of restriction. 1. Guest users have the same access as members (most inclusive), 2. Guest users have limited access to properties and memberships of directory objects (default value), 3. Guest user access is restricted to properties and memberships of their own directory objects (most restrictive). The recommended option is the 3rd, most restrictive: 'Guest user access is restricted to their own directory object'.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/users/users-restrict-guest-permissions", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Then External Identities 4. Select External collaboration settings 5. Under Guest user access, change Guest user access restrictions to be Guest user access is restricted to properties and memberships of their own directory objects", + "Url": "https://learn.microsoft.com/en-us/entra/fundamentals/users-default-permissions#member-and-guest-users" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "This may create additional requests for permissions to access resources that administrators will need to approve. According to https://learn.microsoft.com/en-us/azure/active-directory/enterprise- users/users-restrict-guest-permissions#services-currently-not-supported Service without current support might have compatibility issues with the new guest restriction setting." +} diff --git a/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions.py b/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions.py new file mode 100644 index 0000000000..d4d4981793 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions.py @@ -0,0 +1,29 @@ +from uuid import UUID + +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_policy_guest_users_access_restrictions(Check): + def execute(self) -> Check_Report_Azure: + GUEST_USER_ACCESS_RESTRICTICTED = UUID("2af84b1e-32c8-42b7-82bc-daa82404023b") + findings = [] + + for tenant_domain, auth_policy in entra_client.authorization_policy.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") + report.status_extended = "Guest user access is not restricted to properties and memberships of their own directory objects" + + if ( + getattr(auth_policy, "guest_user_role_id", None) + == GUEST_USER_ACCESS_RESTRICTICTED + ): + report.status = "PASS" + report.status_extended = "Guest user access is restricted to properties and memberships of their own directory objects" + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/__init__.py b/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps.metadata.json b/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps.metadata.json new file mode 100644 index 0000000000..e7a25d29ae --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_policy_restricts_user_consent_for_apps", + "CheckTitle": "Ensure 'User consent for applications' is set to 'Do not allow user consent'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "#microsoft.graph.authorizationPolicy", + "Description": "Require administrators to provide consent for applications before use.", + "Risk": "If Microsoft Entra ID is running as an identity provider for third-party applications, permissions and consent should be limited to administrators or pre-approved. Malicious applications may attempt to exfiltrate data or abuse privileged user accounts.", + "RelatedUrl": "https://learn.microsoft.com/en-gb/entra/identity/enterprise-apps/configure-user-consent?pivots=portal", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/users-can-consent-to-apps-accessing-company-data-on-their-behalf.html#", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Select Enterprise Applications 4. Select Consent and permissions 5. Select User consent settings 6. Set User consent for applications to Do not allow user consent 7. Click save", + "Url": "https://learn.microsoft.com/en-us/security/benchmark/azure/mcsb-privileged-access#pa-1-separate-and-limit-highly-privilegedadministrative-users" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enforcing this setting may create additional requests that administrators need to review." +} diff --git a/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps.py b/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps.py new file mode 100644 index 0000000000..a8fd66af06 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_policy_restricts_user_consent_for_apps(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant_domain, auth_policy in entra_client.authorization_policy.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") + report.status_extended = "Entra allows users to consent apps accessing company data on their behalf" + + if getattr(auth_policy, "default_user_role_permissions", None) and not any( + "ManagePermissionGrantsForSelf" in policy_assigned + for policy_assigned in getattr( + auth_policy.default_user_role_permissions, + "permission_grant_policies_assigned", + ["ManagePermissionGrantsForSelf.microsoft-user-default-legacy"], + ) + ): + report.status = "PASS" + report.status_extended = "Entra does not allow users to consent apps accessing company data on their behalf" + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/__init__.py b/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps.metadata.json b/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps.metadata.json new file mode 100644 index 0000000000..12c6239f6c --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_policy_user_consent_for_verified_apps", + "CheckTitle": "Ensure 'User consent for applications' Is Set To 'Allow for Verified Publishers'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "#microsoft.graph.authorizationPolicy", + "Description": "Allow users to provide consent for selected permissions when a request is coming from a verified publisher.", + "Risk": "If Microsoft Entra ID is running as an identity provider for third-party applications, permissions and consent should be limited to administrators or pre-approved. Malicious applications may attempt to exfiltrate data or abuse privileged user accounts.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal#configure-user-consent-to-applications", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Select Enterprise Applications 4. Select Consent and permissions 5. Select User consent settings 6. Under User consent for applications, select Allow user consent for apps from verified publishers, for selected permissions 7. Select Save", + "Url": "https://learn.microsoft.com/en-us/security/benchmark/azure/mcsb-privileged-access#pa-1-separate-and-limit-highly-privilegedadministrative-users" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enforcing this setting may create additional requests that administrators need to review." +} diff --git a/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps.py b/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps.py new file mode 100644 index 0000000000..47bb46f081 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps.py @@ -0,0 +1,31 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_policy_user_consent_for_verified_apps(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant_domain, auth_policy in entra_client.authorization_policy.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "PASS" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = getattr(auth_policy, "name", "Authorization Policy") + report.resource_id = getattr(auth_policy, "id", "authorizationPolicy") + report.status_extended = "Entra does not allow users to consent non-verified apps accessing company data on their behalf." + + if getattr(auth_policy, "default_user_role_permissions", None) and any( + "ManagePermissionGrantsForSelf.microsoft-user-default-legacy" + in policy_assigned + for policy_assigned in getattr( + auth_policy.default_user_role_permissions, + "permission_grant_policies_assigned", + ["ManagePermissionGrantsForSelf.microsoft-user-default-legacy"], + ) + ): + report.status = "FAIL" + report.status_extended = "Entra allows users to consent apps accessing company data on their behalf." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_security_defaults_enabled/__init__.py b/prowler/providers/azure/services/entra/entra_security_defaults_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled.metadata.json b/prowler/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled.metadata.json new file mode 100644 index 0000000000..ca0c1e2914 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_security_defaults_enabled", + "CheckTitle": "Ensure Security Defaults is enabled on Microsoft Entra ID", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "#microsoft.graph.identitySecurityDefaultsEnforcementPolicy", + "Description": "Security defaults in Microsoft Entra ID make it easier to be secure and help protect your organization. Security defaults contain preconfigured security settings for common attacks. Security defaults is available to everyone. The goal is to ensure that all organizations have a basic level of security enabled at no extra cost. You may turn on security defaults in the Azure portal.", + "Risk": "Security defaults provide secure default settings that we manage on behalf of organizations to keep customers safe until they are ready to manage their own identity security settings. For example, doing the following: - Requiring all users and admins to register for MFA. - Challenging users with MFA - when necessary, based on factors such as location, device, role, and task. - Disabling authentication from legacy authentication clients, which can’t do MFA.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/fundamentals/security-defaults", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/security-defaults-enabled.html#", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu. 2. Browse to Microsoft Entra ID > Properties 3. Select Manage security defaults 4. Set the Enable security defaults to Enabled 5. Select Save", + "Url": "https://techcommunity.microsoft.com/t5/microsoft-entra-blog/introducing-security-defaults/ba-p/1061414" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "This recommendation should be implemented initially and then may be overridden by other service/product specific CIS Benchmarks. Administrators should also be aware that certain configurations in Microsoft Entra ID may impact other Microsoft services such as Microsoft 365." +} diff --git a/prowler/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled.py b/prowler/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled.py new file mode 100644 index 0000000000..e6b2a6f3e7 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_security_defaults_enabled(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for ( + tenant, + security_default, + ) in entra_client.security_default.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant}'" + report.resource_name = getattr(security_default, "name", "Security Default") + report.resource_id = getattr(security_default, "id", "Security Default") + report.status_extended = "Entra security defaults is diabled." + + if getattr(security_default, "is_enabled", False): + report.status = "PASS" + report.status_extended = "Entra security defaults is enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_service.py b/prowler/providers/azure/services/entra/entra_service.py index 5ebc0a9072..8efc8841e0 100644 --- a/prowler/providers/azure/services/entra/entra_service.py +++ b/prowler/providers/azure/services/entra/entra_service.py @@ -1,18 +1,19 @@ import asyncio from dataclasses import dataclass from typing import Optional +from uuid import UUID from msgraph import GraphServiceClient from msgraph.generated.models.default_user_role_permissions import ( DefaultUserRolePermissions, ) +from msgraph.generated.models.setting_value import SettingValue from pydantic import BaseModel from prowler.lib.logger import logger from prowler.providers.azure.lib.service.service import AzureService -########################## Entra class Entra(AzureService): def __init__(self, azure_audit_info): super().__init__(GraphServiceClient, azure_audit_info) @@ -20,10 +21,22 @@ class Entra(AzureService): self.authorization_policy = asyncio.get_event_loop().run_until_complete( self.__get_authorization_policy__() ) + self.group_settings = asyncio.get_event_loop().run_until_complete( + self.__get_group_settings__() + ) + self.security_default = asyncio.get_event_loop().run_until_complete( + self.__get_security_default__() + ) + self.named_locations = asyncio.get_event_loop().run_until_complete( + self.__get_named_locations__() + ) + self.directory_roles = asyncio.get_event_loop().run_until_complete( + self.__get_directory_roles__() + ) async def __get_users__(self): + users = {} try: - users = {} for tenant, client in self.clients.items(): users_list = await client.users.get() users.update({tenant: {}}) @@ -43,8 +56,11 @@ class Entra(AzureService): return users async def __get_authorization_policy__(self): + GUEST_USER_ACCESS_NO_RESTRICTICTED = UUID( + "a0b1b346-4d3e-4e8b-98f8-753987be4970" + ) + authorization_policy = {} try: - authorization_policy = {} for tenant, client in self.clients.items(): auth_policy = await client.policies.authorization_policy.get() authorization_policy.update( @@ -56,6 +72,16 @@ class Entra(AzureService): default_user_role_permissions=getattr( auth_policy, "default_user_role_permissions", None ), + guest_invite_settings=( + auth_policy.allow_invites_from.value + if getattr(auth_policy, "allow_invites_from", None) + else "everyone" + ), + guest_user_role_id=getattr( + auth_policy, + "guest_user_role_id", + GUEST_USER_ACCESS_NO_RESTRICTICTED, + ), ) } ) @@ -66,6 +92,115 @@ class Entra(AzureService): return authorization_policy + async def __get_group_settings__(self): + group_settings = {} + try: + for tenant, client in self.clients.items(): + group_settings_list = await client.group_settings.get() + group_settings.update({tenant: {}}) + for group_setting in group_settings_list.value: + group_settings[tenant].update( + { + group_setting.id: GroupSetting( + name=getattr(group_setting, "display_name", None), + template_id=getattr(group_setting, "template_id", None), + settings=getattr(group_setting, "values", []), + ) + } + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return group_settings + + async def __get_security_default__(self): + try: + security_defaults = {} + for tenant, client in self.clients.items(): + security_default = ( + await client.policies.identity_security_defaults_enforcement_policy.get() + ) + security_defaults.update( + { + tenant: SecurityDefault( + id=security_default.id, + name=security_default.display_name, + is_enabled=security_default.is_enabled, + ), + } + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return security_defaults + + async def __get_named_locations__(self): + named_locations = {} + try: + for tenant, client in self.clients.items(): + named_locations_list = ( + await client.identity.conditional_access.named_locations.get() + ) + named_locations.update({tenant: {}}) + for named_location in getattr(named_locations_list, "value", []): + named_locations[tenant].update( + { + named_location.id: NamedLocation( + name=named_location.display_name, + ip_ranges_addresses=[ + getattr(ip_range, "cidr_address", None) + for ip_range in getattr( + named_location, "ip_ranges", [] + ) + ], + is_trusted=getattr(named_location, "is_trusted", False), + ) + } + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return named_locations + + async def __get_directory_roles__(self): + directory_roles_with_members = {} + try: + for tenant, client in self.clients.items(): + directory_roles_with_members.update({tenant: {}}) + directory_roles = await client.directory_roles.get() + for directory_role in directory_roles.value: + directory_role_members = ( + await client.directory_roles.by_directory_role_id( + directory_role.id + ).members.get() + ) + directory_roles_with_members[tenant].update( + { + directory_role.display_name: DirectoryRole( + id=directory_role.id, + members=[ + User( + id=member.id, + name=member.display_name, + ) + for member in directory_role_members.value + ], + ) + } + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return directory_roles_with_members + class User(BaseModel): id: str @@ -78,3 +213,29 @@ class AuthorizationPolicy: name: str description: str default_user_role_permissions: Optional[DefaultUserRolePermissions] + guest_invite_settings: str + guest_user_role_id: UUID + + +@dataclass +class GroupSetting: + name: Optional[str] + template_id: Optional[str] + settings: list[SettingValue] + + +class SecurityDefault(BaseModel): + id: str + name: str + is_enabled: bool + + +class NamedLocation(BaseModel): + name: str + ip_ranges_addresses: list[str] + is_trusted: bool + + +class DirectoryRole(BaseModel): + id: str + members: list[User] diff --git a/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/__init__.py b/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists.metadata.json b/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists.metadata.json new file mode 100644 index 0000000000..86ade59a19 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_trusted_named_locations_exists", + "CheckTitle": "Ensure Trusted Locations Are Defined", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "#microsoft.graph.ipNamedLocation", + "Description": "Microsoft Entra ID Conditional Access allows an organization to configure Named locations and configure whether those locations are trusted or untrusted. These settings provide organizations the means to specify Geographical locations for use in conditional access policies, or define actual IP addresses and IP ranges and whether or not those IP addresses and/or ranges are trusted by the organization.", + "Risk": "Defining trusted source IP addresses or ranges helps organizations create and enforce Conditional Access policies around those trusted or untrusted IP addresses and ranges. Users authenticating from trusted IP addresses and/or ranges may have less access restrictions or access requirements when compared to users that try to authenticate to Microsoft Entra ID from untrusted locations or untrusted source IP addresses/ranges.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/location-condition", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Navigate to the Microsoft Entra ID Conditional Access Blade 2. Click on the Named locations blade 3. Within the Named locations blade, click on IP ranges location 4. Enter a name for this location setting in the Name text box 5. Click on the + sign 6. Add an IP Address Range in CIDR notation inside the text box that appears 7. Click on the Add button 8. Repeat steps 5 through 7 for each IP Range that needs to be added 9. If the information entered are trusted ranges, select the Mark as trusted location check box 10. Once finished, click on Create", + "Url": "https://learn.microsoft.com/en-us/security/benchmark/azure/mcsb-identity-management#im-7-restrict-resource-access-based-on--conditions" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "When configuring Named locations, the organization can create locations using Geographical location data or by defining source IP addresses or ranges. Configuring Named locations using a Country location does not provide the organization the ability to mark those locations as trusted, and any Conditional Access policy relying on those Countries location setting will not be able to use the All trusted locations setting within the Conditional Access policy. They instead will have to rely on the Select locations setting. This may add additional resource requirements when configuring, and will require thorough organizational testing. In general, Conditional Access policies may completely prevent users from authenticating to Microsoft Entra ID, and thorough testing is recommended. To avoid complete lockout, a 'Break Glass' account with full Global Administrator rights is recommended in the event all other administrators are locked out of authenticating to Microsoft Entra ID. This 'Break Glass' account should be excluded from Conditional Access Policies and should be configured with the longest pass phrase feasible. This account should only be used in the event of an emergency and complete administrator lockout." +} diff --git a/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists.py b/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists.py new file mode 100644 index 0000000000..689796d57a --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_trusted_named_locations_exists(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant, named_locations in entra_client.named_locations.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant}'" + report.resource_name = "Named Locations" + report.resource_id = "Named Locations" + report.status_extended = ( + "There is no trusted location with IP ranges defined." + ) + for named_location_id, named_location in named_locations.items(): + report.resource_name = named_location.name + report.resource_id = named_location_id + + if named_location.ip_ranges_addresses and named_location.is_trusted: + report.status = "PASS" + report.status_extended = f"Exits trusted location with trusted IP ranges, this IPs ranges are: {[ip_range for ip_range in named_location.ip_ranges_addresses if ip_range]}" + break + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/__init__.py b/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups.metadata.json b/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups.metadata.json new file mode 100644 index 0000000000..23e082ff59 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_users_cannot_create_microsoft_365_groups", + "CheckTitle": "Ensure that 'Users can create Microsoft 365 groups in Azure portals, API or PowerShell' is set to 'No'", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Microsoft.Users/Settings", + "Description": "Restrict Microsoft 365 group creation to administrators only.", + "Risk": "Restricting Microsoft 365 group creation to administrators only ensures that creation of Microsoft 365 groups is controlled by the administrator. Appropriate groups should be created and managed by the administrator and group creation rights should not be delegated to any other user.", + "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/community/all-about-groups#microsoft-365-groups", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/users-can-create-office-365-groups.html#", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Then Groups 4. Select General in settings 5. Set Users can create Microsoft 365 groups in Azure portals, API or PowerShell to No", + "Url": "https://learn.microsoft.com/en-us/microsoft-365/solutions/manage-creation-of-groups?view=o365-worldwide&redirectSourcePath=%252fen-us%252farticle%252fControl-who-can-create-Office-365-Groups-4c46c8cb-17d0-44b5-9776-005fced8e618" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enabling this setting could create a number of requests that would need to be managed by an administrator." +} diff --git a/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups.py b/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups.py new file mode 100644 index 0000000000..0928ff96d2 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_users_cannot_create_microsoft_365_groups(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant_domain, group_settings in entra_client.group_settings.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = "Microsoft365 Groups" + report.resource_id = "Microsoft365 Groups" + report.status_extended = "Users can create Microsoft 365 groups." + + for group_setting in group_settings.values(): + if group_setting.name == "Group.Unified": + for setting_value in group_setting.settings: + if ( + getattr(setting_value, "name", "") == "EnableGroupCreation" + and setting_value.value != "true" + ): + report.status = "PASS" + report.status_extended = ( + "Users cannot create Microsoft 365 groups." + ) + break + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/__init__.py b/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins.metadata.json b/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins.metadata.json new file mode 100644 index 0000000000..b966c120c2 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "entra_users_less_than_five_global_admins", + "CheckTitle": "Ensure fewer than 5 users have global administrator assignment", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "#microsoft.graph.directoryRole", + "Description": "This recommendation aims to maintain a balance between security and operational efficiency by ensuring that a minimum of 2 and a maximum of 4 users are assigned the Global Administrator role in Microsoft Entra ID. Having at least two Global Administrators ensures redundancy, while limiting the number to four reduces the risk of excessive privileged access.", + "Risk": "The Global Administrator role has extensive privileges across all services in Microsoft Entra ID. The Global Administrator role should never be used in regular daily activities; administrators should have a regular user account for daily activities, and a separate account for administrative responsibilities. Limiting the number of Global Administrators helps mitigate the risk of unauthorized access, reduces the potential impact of human error, and aligns with the principle of least privilege to reduce the attack surface of an Azure tenant. Conversely, having at least two Global Administrators ensures that administrative functions can be performed without interruption in case of unavailability of a single admin.", + "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", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Microsoft Entra ID 3. Select Roles and Administrators 4. Select Global Administrator 5. Ensure less than 5 users are actively assigned the role. 6. Ensure that at least 2 users are actively assigned the role.", + "Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/add-users/about-admin-roles?view=o365-worldwide#security-guidelines-for-assigning-roles" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Implementing this recommendation may require changes in administrative workflows or the redistribution of roles and responsibilities. Adequate training and awareness should be provided to all Global Administrators." +} diff --git a/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins.py b/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins.py new file mode 100644 index 0000000000..500cb1a9aa --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins.py @@ -0,0 +1,36 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_users_less_than_five_global_admins(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for tenant_domain, directory_roles in entra_client.directory_roles.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = f"Tenant: '{tenant_domain}'" + report.resource_name = "Global Administrator" + + if "Global Administrator" in directory_roles: + report.resource_id = getattr( + directory_roles["Global Administrator"], + "id", + "Global Administrator", + ) + + num_global_admins = len( + getattr(directory_roles["Global Administrator"], "members", []) + ) + + if num_global_admins < 5: + report.status = "PASS" + report.status_extended = ( + f"There are {num_global_admins} global administrators." + ) + else: + report.status_extended = f"There are {num_global_admins} global administrators. It should be less than five." + + findings.append(report) + + return findings diff --git a/tests/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups_test.py b/tests/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups_test.py new file mode 100644 index 0000000000..59e380d990 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_policy_default_users_cannot_create_security_groups/entra_policy_default_users_cannot_create_security_groups_test.py @@ -0,0 +1,130 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_policy_default_users_cannot_create_security_groups: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + entra_client.authorization_policy = {} + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups import ( + entra_policy_default_users_cannot_create_security_groups, + ) + + check = entra_policy_default_users_cannot_create_security_groups() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + entra_client.authorization_policy = {DOMAIN: {}} + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups import ( + entra_policy_default_users_cannot_create_security_groups, + ) + + check = entra_policy_default_users_cannot_create_security_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert ( + result[0].status_extended + == "Non-privileged users are able to create security groups via the Access Panel and the Azure administration portal." + ) + + def test_entra_default_user_role_permissions_allowed_to_create_security_groups( + self, + ): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups import ( + entra_policy_default_users_cannot_create_security_groups, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=mock.MagicMock( + allowed_to_create_security_groups=True + ), + guest_invite_settings="everyone", + guest_user_role_id=None, + ) + } + + check = entra_policy_default_users_cannot_create_security_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Non-privileged users are able to create security groups via the Access Panel and the Azure administration portal." + ) + assert result[0].resource_name == "Test" + assert result[0].resource_id == id + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + + def test_entra_default_user_role_permissions_not_allowed_to_create_security_groups( + self, + ): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups import ( + entra_policy_default_users_cannot_create_security_groups, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=mock.MagicMock( + allowed_to_create_security_groups=False + ), + guest_invite_settings="everyone", + guest_user_role_id=None, + ) + } + + check = entra_policy_default_users_cannot_create_security_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Non-privileged users are not able to create security groups via the Access Panel and the Azure administration portal." + ) + assert result[0].resource_name == "Test" + assert result[0].resource_id == id + assert result[0].subscription == f"Tenant: '{DOMAIN}'" diff --git a/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps_test.py b/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps_test.py index 49cafc123e..6e5e443ef2 100644 --- a/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps_test.py +++ b/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_apps/entra_policy_ensure_default_user_cannot_create_apps_test.py @@ -1,39 +1,12 @@ from unittest import mock from uuid import uuid4 -from prowler.providers.azure.services.entra.entra_service import AuthorizationPolicy +from tests.providers.azure.azure_fixtures import DOMAIN class Test_entra_policy_ensure_default_user_cannot_create_apps: - def test_entra_no_authorization_policy(self): + def test_entra_no_tenants(self): entra_client = mock.MagicMock - entra_client.authorization_policy = {} - - with mock.patch( - "prowler.providers.azure.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.azure.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) == 0 - - def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): - id = str(uuid4()) - entra_client = mock.MagicMock - entra_client.authorization_policy = { - "test.com": AuthorizationPolicy( - id=id, - name="Test", - description="Test", - default_user_role_permissions=mock.MagicMock( - allowed_to_create_apps=False - ), - ) - } with mock.patch( "prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps.entra_client", @@ -43,6 +16,65 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps: entra_policy_ensure_default_user_cannot_create_apps, ) + entra_client.authorization_policy = {} + + check = entra_policy_ensure_default_user_cannot_create_apps() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps import ( + entra_policy_ensure_default_user_cannot_create_apps, + ) + + entra_client.authorization_policy = {DOMAIN: {}} + + check = entra_policy_ensure_default_user_cannot_create_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + 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 + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps import ( + entra_policy_ensure_default_user_cannot_create_apps, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=mock.MagicMock( + allowed_to_create_apps=False + ), + guest_invite_settings="none", + guest_user_role_id=None, + ) + } + check = entra_policy_ensure_default_user_cannot_create_apps() result = check.execute() assert len(result) == 1 @@ -53,21 +85,11 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps: ) assert result[0].resource_name == "Test" assert result[0].resource_id == id - assert result[0].subscription == "All from tenant 'test.com'" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" def test_entra_default_user_role_permissions_allowed_to_create_apps(self): id = str(uuid4()) entra_client = mock.MagicMock - entra_client.authorization_policy = { - "test.com": AuthorizationPolicy( - id=id, - name="Test", - description="Test", - default_user_role_permissions=mock.MagicMock( - allowed_to_create_apps=True - ), - ) - } with mock.patch( "prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps.entra_client", @@ -76,6 +98,22 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps: from prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps import ( entra_policy_ensure_default_user_cannot_create_apps, ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=mock.MagicMock( + allowed_to_create_apps=True + ), + guest_invite_settings="none", + guest_user_role_id=None, + ) + } check = entra_policy_ensure_default_user_cannot_create_apps() result = check.execute() @@ -87,4 +125,4 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps: ) assert result[0].resource_name == "Test" assert result[0].resource_id == id - assert result[0].subscription == "All from tenant 'test.com'" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" diff --git a/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants_test.py b/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants_test.py index 5b54449d97..4ddfa87410 100644 --- a/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants_test.py +++ b/tests/providers/azure/services/entra/entra_policy_ensure_default_user_cannot_create_tenants/entra_policy_ensure_default_user_cannot_create_tenants_test.py @@ -1,11 +1,11 @@ from unittest import mock from uuid import uuid4 -from prowler.providers.azure.services.entra.entra_service import AuthorizationPolicy +from tests.providers.azure.azure_fixtures import DOMAIN class Test_entra_policy_ensure_default_user_cannot_create_tenants: - def test_entra_no_authorization_policy(self): + def test_entra_no_tenants(self): entra_client = mock.MagicMock entra_client.authorization_policy = {} @@ -21,53 +21,9 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants: result = check.execute() assert len(result) == 0 - def test_entra_default_user_role_permissions_not_allowed_to_create_tenants(self): - id = str(uuid4()) + def test_entra_empty_tenant(self): entra_client = mock.MagicMock - entra_client.authorization_policy = { - "test.omnimicrosoft.com": AuthorizationPolicy( - id=id, - name="Test", - description="Test", - default_user_role_permissions=mock.MagicMock( - allowed_to_create_tenants=False - ), - ) - } - - with mock.patch( - "prowler.providers.azure.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.azure.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 == "PASS" - assert ( - result[0].status_extended - == "Tenants creation is disabled for non-admin users." - ) - assert result[0].resource_name == "Test" - assert result[0].resource_id == id - assert result[0].subscription == "All from tenant 'test.omnimicrosoft.com'" - - def test_entra_default_user_role_permissions_allowed_to_create_tenants(self): - id = str(uuid4()) - entra_client = mock.MagicMock - entra_client.authorization_policy = { - "test.omnimicrosoft.com": AuthorizationPolicy( - id=id, - name="Test", - description="Test", - default_user_role_permissions=mock.MagicMock( - allowed_to_create_tenants=True - ), - ) - } + entra_client.authorization_policy = {DOMAIN: {}} with mock.patch( "prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants.entra_client", @@ -77,6 +33,86 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants: 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].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert ( + result[0].status_extended + == "Tenants 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.azure.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.azure.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.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=mock.MagicMock( + allowed_to_create_tenants=False + ), + guest_invite_settings="everyone", + guest_user_role_id=None, + ) + } + + 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 + == "Tenants creation is disabled for non-admin users." + ) + assert result[0].resource_name == "Test" + assert result[0].resource_id == id + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + + def test_entra_default_user_role_permissions_allowed_to_create_tenants(self): + id = str(uuid4()) + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.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.azure.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.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Test", + description="Test", + default_user_role_permissions=mock.MagicMock( + allowed_to_create_tenants=True + ), + guest_invite_settings="everyone", + guest_user_role_id=None, + ) + } + check = entra_policy_ensure_default_user_cannot_create_tenants() result = check.execute() assert len(result) == 1 @@ -87,4 +123,4 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants: ) assert result[0].resource_name == "Test" assert result[0].resource_id == id - assert result[0].subscription == "All from tenant 'test.omnimicrosoft.com'" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" diff --git a/tests/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles_test.py b/tests/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles_test.py new file mode 100644 index 0000000000..4d07d40199 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_policy_guest_invite_only_for_admin_roles/entra_policy_guest_invite_only_for_admin_roles_test.py @@ -0,0 +1,159 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_policy_guest_invite_only_for_admin_roles: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles import ( + entra_policy_guest_invite_only_for_admin_roles, + ) + + entra_client.authorization_policy = {} + + check = entra_policy_guest_invite_only_for_admin_roles() + result = check.execute() + assert len(result) == 0 + + def test_entra_empty_tenant(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles import ( + entra_policy_guest_invite_only_for_admin_roles, + ) + + entra_client.authorization_policy = {DOMAIN: {}} + + check = entra_policy_guest_invite_only_for_admin_roles() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert ( + result[0].status_extended + == "Guest invitations are not restricted to users with specific administrative roles only." + ) + + def test_entra_tenant_policy_allow_invites_from_everyone(self): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles import ( + entra_policy_guest_invite_only_for_admin_roles, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="TestPolicy", + description="TestPolicyDescription", + default_user_role_permissions=None, + guest_invite_settings="everyone", + guest_user_role_id=None, + ) + } + + check = entra_policy_guest_invite_only_for_admin_roles() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "Guest invitations are not restricted to users with specific administrative roles only." + ) + assert result[0].resource_name == "TestPolicy" + assert result[0].resource_id == id + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + + def test_entra_tenant_policy_allow_invites_from_admins(self): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles import ( + entra_policy_guest_invite_only_for_admin_roles, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="TestPolicy", + description="TestPolicyDescription", + default_user_role_permissions=None, + guest_invite_settings="adminsAndGuestInviters", + guest_user_role_id=None, + ) + } + + check = entra_policy_guest_invite_only_for_admin_roles() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "Guest invitations are restricted to users with specific administrative roles only." + ) + assert result[0].resource_name == "TestPolicy" + assert result[0].resource_id == id + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + + def test_entra_tenant_policy_allow_invites_from_none(self): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles import ( + entra_policy_guest_invite_only_for_admin_roles, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="TestPolicy", + description="TestPolicyDescription", + default_user_role_permissions=None, + guest_invite_settings="none", + guest_user_role_id=None, + ) + } + + check = entra_policy_guest_invite_only_for_admin_roles() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "Guest invitations are restricted to users with specific administrative roles only." + ) + assert result[0].resource_name == "TestPolicy" + assert result[0].resource_id == id + assert result[0].subscription == f"Tenant: '{DOMAIN}'" diff --git a/tests/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions_test.py b/tests/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions_test.py new file mode 100644 index 0000000000..7382ac8607 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_policy_guest_users_access_restrictions/entra_policy_guest_users_access_restrictions_test.py @@ -0,0 +1,162 @@ +from unittest import mock +from uuid import UUID, uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_policy_guest_users_access_restrictions: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions import ( + entra_policy_guest_users_access_restrictions, + ) + + entra_client.authorization_policy = {} + + check = entra_policy_guest_users_access_restrictions() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions import ( + entra_policy_guest_users_access_restrictions, + ) + + entra_client.authorization_policy = {DOMAIN: {}} + + check = entra_policy_guest_users_access_restrictions() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert ( + result[0].status_extended + == "Guest user access is not restricted to properties and memberships of their own directory objects" + ) + + def test_entra_tenant_policy_access_same_as_member(self): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions import ( + entra_policy_guest_users_access_restrictions, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Authorization Policy", + description="", + default_user_role_permissions=None, + guest_invite_settings=None, + guest_user_role_id=UUID("a0b1b346-4d3e-4e8b-98f8-753987be4970"), + ) + } + + check = entra_policy_guest_users_access_restrictions() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == id + assert ( + result[0].status_extended + == "Guest user access is not restricted to properties and memberships of their own directory objects" + ) + + def test_entra_tenant_policy_limited_access(self): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions import ( + entra_policy_guest_users_access_restrictions, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Authorization Policy", + description="", + default_user_role_permissions=None, + guest_invite_settings=None, + guest_user_role_id=UUID("10dae51f-b6af-4016-8d66-8c2a99b929b3"), + ) + } + + check = entra_policy_guest_users_access_restrictions() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == id + assert ( + result[0].status_extended + == "Guest user access is not restricted to properties and memberships of their own directory objects" + ) + + def test_entra_tenant_policy_access_restricted(self): + entra_client = mock.MagicMock + id = str(uuid4()) + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions import ( + entra_policy_guest_users_access_restrictions, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + entra_client.authorization_policy = { + DOMAIN: AuthorizationPolicy( + id=id, + name="Authorization Policy", + description="", + default_user_role_permissions=None, + guest_invite_settings=None, + guest_user_role_id=UUID("2af84b1e-32c8-42b7-82bc-daa82404023b"), + ) + } + + check = entra_policy_guest_users_access_restrictions() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == id + assert ( + result[0].status_extended + == "Guest user access is restricted to properties and memberships of their own directory objects" + ) diff --git a/tests/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps_test.py b/tests/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps_test.py new file mode 100644 index 0000000000..9802dd6697 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_policy_restricts_user_consent_for_apps/entra_policy_restricts_user_consent_for_apps_test.py @@ -0,0 +1,167 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_policy_restricts_user_consent_for_apps: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps import ( + entra_policy_restricts_user_consent_for_apps, + ) + + entra_client.authorization_policy = {} + + check = entra_policy_restricts_user_consent_for_apps() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps import ( + entra_policy_restricts_user_consent_for_apps, + ) + + entra_client.authorization_policy = {DOMAIN: {}} + + check = entra_policy_restricts_user_consent_for_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == "authorizationPolicy" + assert ( + result[0].status_extended + == "Entra allows users to consent apps accessing company data on their behalf" + ) + + def test_entra_tenant_no_default_user_role_permissions(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps import ( + entra_policy_restricts_user_consent_for_apps, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + auth_policy = AuthorizationPolicy( + id=uuid4(), + name="Authorization Policy", + description="Authorization Policy Description", + default_user_role_permissions=None, + guest_invite_settings="none", + guest_user_role_id=None, + ) + + entra_client.authorization_policy = {DOMAIN: auth_policy} + + check = entra_policy_restricts_user_consent_for_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == auth_policy.id + assert ( + result[0].status_extended + == "Entra allows users to consent apps accessing company data on their behalf" + ) + + def test_entra_tenant_no_consent(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps import ( + entra_policy_restricts_user_consent_for_apps, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + def_user_role_permissions = mock.MagicMock + def_user_role_permissions.permission_grant_policies_assigned = [] + + auth_policy = AuthorizationPolicy( + id=uuid4(), + name="Authorization Policy", + description="Authorization Policy Description", + default_user_role_permissions=def_user_role_permissions, + guest_invite_settings="none", + guest_user_role_id=None, + ) + + entra_client.authorization_policy = {DOMAIN: auth_policy} + + check = entra_policy_restricts_user_consent_for_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == auth_policy.id + assert ( + result[0].status_extended + == "Entra does not allow users to consent apps accessing company data on their behalf" + ) + + def test_entra_tenant_legacy_consent(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps import ( + entra_policy_restricts_user_consent_for_apps, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + def_user_role_permissions = mock.MagicMock + def_user_role_permissions.permission_grant_policies_assigned = [ + "ManagePermissionGrantsForSelf.microsoft-user-default-legacy" + ] + + auth_policy = AuthorizationPolicy( + id=uuid4(), + name="Authorization Policy", + description="Authorization Policy Description", + default_user_role_permissions=def_user_role_permissions, + guest_invite_settings="none", + guest_user_role_id=None, + ) + + entra_client.authorization_policy = {DOMAIN: auth_policy} + + check = entra_policy_restricts_user_consent_for_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == auth_policy.id + assert ( + result[0].status_extended + == "Entra allows users to consent apps accessing company data on their behalf" + ) diff --git a/tests/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps_test.py b/tests/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps_test.py new file mode 100644 index 0000000000..48d11fa9b2 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_policy_user_consent_for_verified_apps/entra_policy_user_consent_for_verified_apps_test.py @@ -0,0 +1,105 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_policy_user_consent_for_verified_apps: + def test_entra_no_subscriptions(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_user_consent_for_verified_apps.entra_policy_user_consent_for_verified_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_user_consent_for_verified_apps.entra_policy_user_consent_for_verified_apps import ( + entra_policy_user_consent_for_verified_apps, + ) + + entra_client.authorization_policy = {} + + check = entra_policy_user_consent_for_verified_apps() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_no_consent(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_user_consent_for_verified_apps.entra_policy_user_consent_for_verified_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_user_consent_for_verified_apps.entra_policy_user_consent_for_verified_apps import ( + entra_policy_user_consent_for_verified_apps, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + def_user_role_permissions = mock.MagicMock + def_user_role_permissions.permission_grant_policies_assigned = [] + + auth_policy = AuthorizationPolicy( + id=uuid4(), + name="Authorization Policy", + description="Authorization Policy Description", + default_user_role_permissions=def_user_role_permissions, + guest_invite_settings="none", + guest_user_role_id=None, + ) + + entra_client.authorization_policy = {DOMAIN: auth_policy} + + check = entra_policy_user_consent_for_verified_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == auth_policy.id + assert ( + result[0].status_extended + == "Entra does not allow users to consent non-verified apps accessing company data on their behalf." + ) + + def test_entra_tenant_legacy_consent(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_policy_user_consent_for_verified_apps.entra_policy_user_consent_for_verified_apps.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_policy_user_consent_for_verified_apps.entra_policy_user_consent_for_verified_apps import ( + entra_policy_user_consent_for_verified_apps, + ) + from prowler.providers.azure.services.entra.entra_service import ( + AuthorizationPolicy, + ) + + def_user_role_permissions = mock.MagicMock + def_user_role_permissions.permission_grant_policies_assigned = [ + "ManagePermissionGrantsForSelf.microsoft-user-default-legacy" + ] + + auth_policy = AuthorizationPolicy( + id=uuid4(), + name="Authorization Policy", + description="Authorization Policy Description", + default_user_role_permissions=def_user_role_permissions, + guest_invite_settings="none", + guest_user_role_id=None, + ) + + entra_client.authorization_policy = {DOMAIN: auth_policy} + + check = entra_policy_user_consent_for_verified_apps() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Authorization Policy" + assert result[0].resource_id == auth_policy.id + assert ( + result[0].status_extended + == "Entra allows users to consent apps accessing company data on their behalf." + ) diff --git a/tests/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled_test.py b/tests/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled_test.py new file mode 100644 index 0000000000..e1d33c364b --- /dev/null +++ b/tests/providers/azure/services/entra/entra_security_defaults_enabled/entra_security_defaults_enabled_test.py @@ -0,0 +1,103 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_security_defaults_enabled: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled import ( + entra_security_defaults_enabled, + ) + + entra_client.security_default = {} + + check = entra_security_defaults_enabled() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled import ( + entra_security_defaults_enabled, + ) + + entra_client.security_default = {DOMAIN: {}} + + check = entra_security_defaults_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Entra security defaults is diabled." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Security Default" + assert result[0].resource_id == "Security Default" + + def test_entra_security_default_enabled(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled import ( + entra_security_defaults_enabled, + ) + from prowler.providers.azure.services.entra.entra_service import ( + SecurityDefault, + ) + + id = str(uuid4()) + + entra_client.security_default = { + DOMAIN: SecurityDefault(id=id, name="Sec Default", is_enabled=True) + } + + check = entra_security_defaults_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == "Entra security defaults is enabled." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Sec Default" + assert result[0].resource_id == id + + def test_entra_security_default_disabled(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_security_defaults_enabled.entra_security_defaults_enabled import ( + entra_security_defaults_enabled, + ) + from prowler.providers.azure.services.entra.entra_service import ( + SecurityDefault, + ) + + id = str(uuid4()) + + entra_client.security_default = { + DOMAIN: SecurityDefault(id=id, name="Sec Default", is_enabled=False) + } + + check = entra_security_defaults_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Entra security defaults is diabled." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Sec Default" + assert result[0].resource_id == id diff --git a/tests/providers/azure/services/entra/entra_service_test.py b/tests/providers/azure/services/entra/entra_service_test.py index 2cd26bf58f..890b066a27 100644 --- a/tests/providers/azure/services/entra/entra_service_test.py +++ b/tests/providers/azure/services/entra/entra_service_test.py @@ -2,7 +2,11 @@ from unittest.mock import patch from prowler.providers.azure.services.entra.entra_service import ( AuthorizationPolicy, + DirectoryRole, Entra, + GroupSetting, + NamedLocation, + SecurityDefault, User, ) from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_audit_info @@ -10,17 +14,68 @@ from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_audit_ async def mock_entra_get_users(_): return { - "user-1@tenant1.es": User(id="id-1", name="User 1"), + DOMAIN: { + "user-1@tenant1.es": User(id="id-1", name="User 1"), + } } async def mock_entra_get_authorization_policy(_): - return AuthorizationPolicy( - id="id-1", - name="Name 1", - description="Description 1", - default_user_role_permissions=None, - ) + return { + DOMAIN: AuthorizationPolicy( + id="id-1", + name="Name 1", + description="Description 1", + default_user_role_permissions=None, + guest_invite_settings="none", + guest_user_role_id=None, + ) + } + + +async def mock_entra_get_group_settings(_): + return { + DOMAIN: { + "id-1": GroupSetting( + name="Test", + template_id="id-group-setting", + settings=[], + ) + } + } + + +async def mock_entra_get_security_default(_): + return { + DOMAIN: SecurityDefault( + id="id-security-default", + name="Test", + is_enabled=True, + ) + } + + +async def mock_entra_get_named_locations(_): + return { + DOMAIN: { + "id-1": NamedLocation( + name="Test", + ip_ranges_addresses=[], + is_trusted=False, + ) + } + } + + +async def mock_entra_get_directory_roles(_): + return { + DOMAIN: { + "GlobalAdministrator": DirectoryRole( + id="id-directory-role", + members=[], + ) + } + } @patch( @@ -31,6 +86,22 @@ async def mock_entra_get_authorization_policy(_): "prowler.providers.azure.services.entra.entra_service.Entra.__get_authorization_policy__", new=mock_entra_get_authorization_policy, ) +@patch( + "prowler.providers.azure.services.entra.entra_service.Entra.__get_group_settings__", + new=mock_entra_get_group_settings, +) +@patch( + "prowler.providers.azure.services.entra.entra_service.Entra.__get_security_default__", + new=mock_entra_get_security_default, +) +@patch( + "prowler.providers.azure.services.entra.entra_service.Entra.__get_named_locations__", + new=mock_entra_get_named_locations, +) +@patch( + "prowler.providers.azure.services.entra.entra_service.Entra.__get_directory_roles__", + new=mock_entra_get_directory_roles, +) class Test_Entra_Service: def test__get_client__(self): entra_client = Entra(set_mocked_azure_audit_info()) @@ -43,12 +114,48 @@ class Test_Entra_Service: def test__get_users__(self): entra_client = Entra(set_mocked_azure_audit_info()) assert len(entra_client.users) == 1 - assert entra_client.users["user-1@tenant1.es"].id == "id-1" - assert entra_client.users["user-1@tenant1.es"].name == "User 1" + assert entra_client.users[DOMAIN]["user-1@tenant1.es"].id == "id-1" + assert entra_client.users[DOMAIN]["user-1@tenant1.es"].name == "User 1" def test__get_authorization_policy__(self): entra_client = Entra(set_mocked_azure_audit_info()) - 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 not entra_client.authorization_policy.default_user_role_permissions + assert entra_client.authorization_policy[DOMAIN].id == "id-1" + assert entra_client.authorization_policy[DOMAIN].name == "Name 1" + assert entra_client.authorization_policy[DOMAIN].description == "Description 1" + assert not entra_client.authorization_policy[ + DOMAIN + ].default_user_role_permissions + + def test__get_group_settings__(self): + entra_client = Entra(set_mocked_azure_audit_info()) + assert entra_client.group_settings[DOMAIN]["id-1"].name == "Test" + assert ( + entra_client.group_settings[DOMAIN]["id-1"].template_id + == "id-group-setting" + ) + assert len(entra_client.group_settings[DOMAIN]["id-1"].settings) == 0 + + def test__get_security_default__(self): + entra_client = Entra(set_mocked_azure_audit_info()) + assert entra_client.security_default[DOMAIN].id == "id-security-default" + assert entra_client.security_default[DOMAIN].name == "Test" + assert entra_client.security_default[DOMAIN].is_enabled + + def test__get_named_locations__(self): + entra_client = Entra(set_mocked_azure_audit_info()) + assert entra_client.named_locations[DOMAIN]["id-1"].name == "Test" + assert ( + len(entra_client.named_locations[DOMAIN]["id-1"].ip_ranges_addresses) == 0 + ) + assert not entra_client.named_locations[DOMAIN]["id-1"].is_trusted + + def test__get_directory_roles__(self): + entra_client = Entra(set_mocked_azure_audit_info()) + assert ( + entra_client.directory_roles[DOMAIN]["GlobalAdministrator"].id + == "id-directory-role" + ) + assert ( + len(entra_client.directory_roles[DOMAIN]["GlobalAdministrator"].members) + == 0 + ) diff --git a/tests/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists_test.py b/tests/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists_test.py new file mode 100644 index 0000000000..6d633ac2f0 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_trusted_named_locations_exists/entra_trusted_named_locations_exists_test.py @@ -0,0 +1,153 @@ +from unittest import mock + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_trusted_named_locations_exists: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists import ( + entra_trusted_named_locations_exists, + ) + + entra_client.named_locations = {} + + check = entra_trusted_named_locations_exists() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists import ( + entra_trusted_named_locations_exists, + ) + + entra_client.named_locations = {DOMAIN: {}} + + check = entra_trusted_named_locations_exists() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "There is no trusted location with IP ranges defined." + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Named Locations" + assert result[0].resource_id == "Named Locations" + + def test_entra_named_location_with_ip_ranges(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + NamedLocation, + ) + from prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists import ( + entra_trusted_named_locations_exists, + ) + + entra_client.named_locations = { + DOMAIN: { + "location_id": NamedLocation( + name="Test Location", + ip_ranges_addresses=["192.168.0.1/24"], + is_trusted=True, + ) + } + } + + check = entra_trusted_named_locations_exists() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Exits trusted location with trusted IP ranges, this IPs ranges are: ['192.168.0.1/24']" + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Test Location" + assert result[0].resource_id == "location_id" + + def test_entra_named_location_without_ip_ranges(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + NamedLocation, + ) + from prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists import ( + entra_trusted_named_locations_exists, + ) + + entra_client.named_locations = { + DOMAIN: { + "location_id": NamedLocation( + name="Test Location", ip_ranges_addresses=[], is_trusted=True + ) + } + } + + check = entra_trusted_named_locations_exists() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "There is no trusted location with IP ranges defined." + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Test Location" + assert result[0].resource_id == "location_id" + + def test_entra_new_named_location_with_ip_ranges_not_trusted(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + NamedLocation, + ) + from prowler.providers.azure.services.entra.entra_trusted_named_locations_exists.entra_trusted_named_locations_exists import ( + entra_trusted_named_locations_exists, + ) + + entra_client.named_locations = { + DOMAIN: { + "location_id": NamedLocation( + name="Test Location", + ip_ranges_addresses=["192.168.0.1/24"], + is_trusted=False, + ) + } + } + + check = entra_trusted_named_locations_exists() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "There is no trusted location with IP ranges defined." + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Test Location" + assert result[0].resource_id == "location_id" diff --git a/tests/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups_test.py b/tests/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups_test.py new file mode 100644 index 0000000000..e8fc9415a9 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_users_cannot_create_microsoft_365_groups/entra_users_cannot_create_microsoft_365_groups_test.py @@ -0,0 +1,163 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_users_cannot_create_microsoft_365_groups: + def test_entra_no_tenant(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups import ( + entra_users_cannot_create_microsoft_365_groups, + ) + + entra_client.group_settings = {} + + check = entra_users_cannot_create_microsoft_365_groups() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups import ( + entra_users_cannot_create_microsoft_365_groups, + ) + + entra_client.group_settings = {DOMAIN: {}} + + check = entra_users_cannot_create_microsoft_365_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Users can create Microsoft 365 groups." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Microsoft365 Groups" + assert result[0].resource_id == "Microsoft365 Groups" + + def test_entra_users_cannot_create_microsoft_365_groups(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + GroupSetting, + ) + from prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups import ( + entra_users_cannot_create_microsoft_365_groups, + ) + + id = str(uuid4()) + template_id = str(uuid4()) + + setting = mock.MagicMock + setting.name = "EnableGroupCreation" + setting.value = "false" + + entra_client.group_settings = { + DOMAIN: { + id: GroupSetting( + name="Group.Unified", + template_id=template_id, + settings=[setting], + ) + } + } + + check = entra_users_cannot_create_microsoft_365_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "Users cannot create Microsoft 365 groups." + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Microsoft365 Groups" + assert result[0].resource_id == "Microsoft365 Groups" + + def test_entra_users_can_create_microsoft_365_groups(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + GroupSetting, + ) + from prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups import ( + entra_users_cannot_create_microsoft_365_groups, + ) + + id = str(uuid4()) + template_id = str(uuid4()) + + setting = mock.MagicMock + setting.name = "EnableGroupCreation" + setting.value = "true" + + entra_client.group_settings = { + DOMAIN: { + id: GroupSetting( + name="Group.Unified", + template_id=template_id, + settings=[setting], + ) + } + } + + check = entra_users_cannot_create_microsoft_365_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Users can create Microsoft 365 groups." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Microsoft365 Groups" + assert result[0].resource_id == "Microsoft365 Groups" + + def test_entra_users_can_create_microsoft_365_groups_no_setting(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + GroupSetting, + ) + from prowler.providers.azure.services.entra.entra_users_cannot_create_microsoft_365_groups.entra_users_cannot_create_microsoft_365_groups import ( + entra_users_cannot_create_microsoft_365_groups, + ) + + id = str(uuid4()) + template_id = str(uuid4()) + + entra_client.group_settings = { + DOMAIN: { + id: GroupSetting( + name="Group.Unified", + template_id=template_id, + settings=[], + ) + } + } + + check = entra_users_cannot_create_microsoft_365_groups() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Users can create Microsoft 365 groups." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Microsoft365 Groups" + assert result[0].resource_id == "Microsoft365 Groups" diff --git a/tests/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins_test.py b/tests/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins_test.py new file mode 100644 index 0000000000..b777bb1122 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_users_less_than_five_global_admins/entra_users_less_than_five_global_admins_test.py @@ -0,0 +1,180 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN + + +class Test_entra_users_less_than_five_global_admins: + def test_entra_no_tenants(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import ( + entra_users_less_than_five_global_admins, + ) + + entra_client.directory_roles = {} + + check = entra_users_less_than_five_global_admins() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_empty(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import ( + entra_users_less_than_five_global_admins, + ) + + entra_client.directory_roles = {DOMAIN: {}} + + check = entra_users_less_than_five_global_admins() + result = check.execute() + assert len(result) == 0 + + def test_entra_less_than_five_global_admins(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + DirectoryRole, + User, + ) + from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import ( + entra_users_less_than_five_global_admins, + ) + + id = str(uuid4()) + id_user1 = str(uuid4()) + id_user2 = str(uuid4()) + + entra_client.directory_roles = { + DOMAIN: { + "Global Administrator": DirectoryRole( + id=id, + members=[ + User(id=id_user1, name="User1"), + User(id=id_user2, name="User2"), + ], + ) + } + } + + check = entra_users_less_than_five_global_admins() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == "There are 2 global administrators." + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Global Administrator" + assert result[0].resource_id == id + + def test_entra_more_than_five_global_admins(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + DirectoryRole, + User, + ) + from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import ( + entra_users_less_than_five_global_admins, + ) + + id = str(uuid4()) + id_user1 = str(uuid4()) + id_user2 = str(uuid4()) + id_user3 = str(uuid4()) + id_user4 = str(uuid4()) + id_user5 = str(uuid4()) + id_user6 = str(uuid4()) + + entra_client.directory_roles = { + DOMAIN: { + "Global Administrator": DirectoryRole( + id=id, + members=[ + User(id=id_user1, name="User1"), + User(id=id_user2, name="User2"), + User(id=id_user3, name="User3"), + User(id=id_user4, name="User4"), + User(id=id_user5, name="User5"), + User(id=id_user6, name="User6"), + ], + ) + } + } + + check = entra_users_less_than_five_global_admins() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "There are 6 global administrators. It should be less than five." + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Global Administrator" + assert result[0].resource_id == id + + def test_entra_exactly_five_global_admins(self): + entra_client = mock.MagicMock + + with mock.patch( + "prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client", + new=entra_client, + ): + from prowler.providers.azure.services.entra.entra_service import ( + DirectoryRole, + User, + ) + from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import ( + entra_users_less_than_five_global_admins, + ) + + id = str(uuid4()) + id_user1 = str(uuid4()) + id_user2 = str(uuid4()) + id_user3 = str(uuid4()) + id_user4 = str(uuid4()) + id_user5 = str(uuid4()) + + entra_client.directory_roles = { + DOMAIN: { + "Global Administrator": DirectoryRole( + id=id, + members=[ + User(id=id_user1, name="User1"), + User(id=id_user2, name="User2"), + User(id=id_user3, name="User3"), + User(id=id_user4, name="User4"), + User(id=id_user5, name="User5"), + ], + ) + } + } + + check = entra_users_less_than_five_global_admins() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "There are 5 global administrators. It should be less than five." + ) + assert result[0].subscription == f"Tenant: '{DOMAIN}'" + assert result[0].resource_name == "Global Administrator" + assert result[0].resource_id == id