feat(azure) New Microsoft Entra ID checks (#3610)

This commit is contained in:
Rubén De la Torre Vico
2024-03-27 14:17:15 +01:00
committed by GitHub
parent dabb2acfcc
commit ac60b6f08d
17 changed files with 985 additions and 9 deletions
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "entra_conditional_access_policy_require_mfa_for_management_api",
"CheckTitle": "Ensure Multifactor Authentication is Required for Windows Azure Service Management API",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "#microsoft.graph.conditionalAccess",
"Description": "This recommendation ensures that users accessing the Windows Azure Service Management API (i.e. Azure Powershell, Azure CLI, Azure Resource Manager API, etc.) are required to use multifactor authentication (MFA) credentials when accessing resources through the Windows Azure Service Management API.",
"Risk": "Administrative access to the Windows Azure Service Management API should be secured with a higher level of scrutiny to authenticating mechanisms. Enabling multifactor authentication is recommended to reduce the potential for abuse of Administrative actions, and to prevent intruders or compromised admin credentials from changing administrative settings.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-azure-management",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "1. From the Azure Admin Portal dashboard, open Microsoft Entra ID. 2. Click Security in the Entra ID blade. 3. Click Conditional Access in the Security blade. 4. Click Policies in the Conditional Access blade. 5. Click + New policy. 6. Enter a name for the policy. 7. Click the blue text under Users. 8. Under Include, select All users. 9. Under Exclude, check Users and groups. 10. Select users or groups to be exempted from this policy (e.g. break-glass emergency accounts, and non-interactive service accounts) then click the Select button. 11. Click the blue text under Target Resources. 12. Under Include, click the Select apps radio button. 13. Click the blue text under Select. 14. Check the box next to Windows Azure Service Management APIs then click the Select button. 15. Click the blue text under Grant. 16. Under Grant access check the box for Require multifactor authentication then click the Select button. 17. Before creating, set Enable policy to Report-only. 18. Click Create. After testing the policy in report-only mode, update the Enable policy setting from Report-only to On.",
"Url": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-cloud-apps"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Conditional Access policies require Microsoft Entra ID P1 or P2 licenses. Similarly, they may require additional overhead to maintain if users lose access to their MFA. Any users or groups which are granted an exception to this policy should be carefully tracked, be granted only minimal necessary privileges, and conditional access exceptions should be regularly reviewed or investigated."
}
@@ -0,0 +1,44 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.entra.entra_client import entra_client
class entra_conditional_access_policy_require_mfa_for_management_api(Check):
def execute(self) -> Check_Report_Azure:
findings = []
WINDOWS_AZURE_SERVICE_MANAGEMENT_API = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
for (
tenant_name,
conditional_access_policies,
) in entra_client.conditional_access_policy.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.subscription = f"Tenant: {tenant_name}"
report.resource_name = "Conditional Access Policy"
report.resource_id = "Conditional Access Policy"
report.status_extended = (
"Conditional Access Policy does not require MFA for management API."
)
for policy_id, policy in conditional_access_policies.items():
if (
policy.state == "enabled"
and "All" in policy.users["include"]
and WINDOWS_AZURE_SERVICE_MANAGEMENT_API
in policy.target_resources["include"]
and any(
"mfa" in access_control.lower()
for access_control in policy.access_controls["grant"]
)
):
report.status = "PASS"
report.status_extended = (
"Conditional Access Policy requires MFA for management API."
)
report.resource_id = policy_id
report.resource_name = policy.name
break
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "entra_non_privileged_user_has_mfa",
"CheckTitle": "Ensure that 'Multi-Factor Auth Status' is 'Enabled' for all Non-Privileged Users",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "#microsoft.graph.users",
"Description": "Enable multi-factor authentication for all non-privileged users.",
"Risk": "Multi-factor authentication requires an individual to present a minimum of two separate forms of authentication before access is granted. Multi-factor authentication provides additional assurance that the individual attempting to gain access is who they claim to be. With multi-factor authentication, an attacker would need to compromise at least two different authentication mechanisms, increasing the difficulty of compromise and thus reducing the risk.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-howitworks",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/multi-factor-authentication-for-all-non-privileged-users.html#",
"Terraform": ""
},
"Recommendation": {
"Text": "Activate one of the available multi-factor authentication methods for users in Microsoft Entra ID.",
"Url": "https://learn.microsoft.com/en-us/entra/identity/authentication/tutorial-enable-azure-mfa"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Users would require two forms of authentication before any access is granted. Also, this requires an overhead for managing dual forms of authentication."
}
@@ -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
from prowler.providers.azure.services.entra.lib.user_privileges import (
is_privileged_user,
)
class entra_non_privileged_user_has_mfa(Check):
def execute(self) -> Check_Report_Azure:
findings = []
for tenant_domain, users in entra_client.users.items():
for user_domain_name, user in users.items():
if not is_privileged_user(
user, entra_client.directory_roles[tenant_domain]
):
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.subscription = f"Tenant: {tenant_domain}"
report.resource_name = user_domain_name
report.resource_id = user.id
report.status_extended = (
f"User '{user.name}' does not have MFA enabled."
)
if len(user.authentication_methods) > 1:
report.status = "PASS"
report.status_extended = f"User '{user.name}' has MFA enabled."
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "entra_privileged_user_has_mfa",
"CheckTitle": "Ensure that 'Multi-Factor Auth Status' is 'Enabled' for all Privileged Users",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "#microsoft.graph.users",
"Description": "Enable multi-factor authentication for all roles, groups, and users that have write access or permissions to Azure resources. These include custom created objects or built-in roles such as; - Service Co-Administrators - Subscription Owners - Contributors",
"Risk": "Multi-factor authentication requires an individual to present a minimum of two separate forms of authentication before access is granted. Multi-factor authentication provides additional assurance that the individual attempting to gain access is who they claim to be. With multi-factor authentication, an attacker would need to compromise at least two different authentication mechanisms, increasing the difficulty of compromise and thus reducing the risk.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-howitworks",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActiveDirectory/multi-factor-authentication-for-all-privileged-users.html#",
"Terraform": ""
},
"Recommendation": {
"Text": "Activate one of the available multi-factor authentication methods for users in Microsoft Entra ID.",
"Url": "https://learn.microsoft.com/en-us/entra/identity/authentication/tutorial-enable-azure-mfa"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Users would require two forms of authentication before any access is granted. Additional administrative time will be required for managing dual forms of authentication when enabling multi-factor authentication."
}
@@ -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
from prowler.providers.azure.services.entra.lib.user_privileges import (
is_privileged_user,
)
class entra_privileged_user_has_mfa(Check):
def execute(self) -> Check_Report_Azure:
findings = []
for tenant_domain, users in entra_client.users.items():
for user_domain_name, user in users.items():
if is_privileged_user(
user, entra_client.directory_roles[tenant_domain]
):
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.subscription = f"Tenant: {tenant_domain}"
report.resource_name = user_domain_name
report.resource_id = user.id
report.status_extended = (
f"User '{user.name}' does not have MFA enabled."
)
if len(user.authentication_methods) > 1:
report.status = "PASS"
report.status_extended = f"User '{user.name}' has MFA enabled."
findings.append(report)
return findings
@@ -1,6 +1,6 @@
import asyncio
from dataclasses import dataclass
from typing import Optional
from typing import Any, List, Optional
from uuid import UUID
from msgraph import GraphServiceClient
@@ -33,6 +33,9 @@ class Entra(AzureService):
self.directory_roles = asyncio.get_event_loop().run_until_complete(
self.__get_directory_roles__()
)
self.conditional_access_policy = asyncio.get_event_loop().run_until_complete(
self.__get_conditional_access_policy__()
)
async def __get_users__(self):
users = {}
@@ -44,7 +47,13 @@ class Entra(AzureService):
users[tenant].update(
{
user.user_principal_name: User(
id=user.id, name=user.display_name
id=user.id,
name=user.display_name,
authentication_methods=(
await client.users.by_user_id(
user.id
).authentication.methods.get()
).value,
)
}
)
@@ -185,11 +194,11 @@ class Entra(AzureService):
directory_role.display_name: DirectoryRole(
id=directory_role.id,
members=[
User(
id=member.id,
name=member.display_name,
)
self.users[tenant][member.user_principal_name]
for member in directory_role_members.value
if self.users[tenant].get(
member.user_principal_name, None
)
],
)
}
@@ -201,10 +210,88 @@ class Entra(AzureService):
)
return directory_roles_with_members
async def __get_conditional_access_policy__(self):
conditional_access_policy = {}
try:
for tenant, client in self.clients.items():
conditional_access_policies = (
await client.identity.conditional_access.policies.get()
)
conditional_access_policy.update({tenant: {}})
for policy in getattr(conditional_access_policies, "value", []):
conditions = getattr(policy, "conditions", None)
included_apps = []
excluded_apps = []
if getattr(conditions, "applications", None):
if getattr(conditions.applications, "include_applications", []):
included_apps = conditions.applications.include_applications
elif getattr(
conditions.applications, "include_user_actions", []
):
included_apps = conditions.applications.include_user_actions
if getattr(conditions.applications, "exclude_applications", []):
excluded_apps = conditions.applications.exclude_applications
elif getattr(
conditions.applications, "exclude_user_actions", []
):
excluded_apps = conditions.applications.exclude_user_actions
grant_access_controls = []
block_access_controls = []
for access_control in (
getattr(policy.grant_controls, "built_in_controls")
if policy.grant_controls
else []
):
if "Grant" in str(access_control):
grant_access_controls.append(str(access_control))
else:
block_access_controls.append(str(access_control))
conditional_access_policy[tenant].update(
{
policy.id: ConditionalAccessPolicy(
name=policy.display_name,
state=getattr(policy, "state", "None"),
users={
"include": (
getattr(conditions.users, "include_users", [])
if getattr(conditions, "users", None)
else []
),
"exclude": (
getattr(conditions.users, "exclude_users", [])
if getattr(conditions, "users", None)
else []
),
},
target_resources={
"include": included_apps,
"exclude": excluded_apps,
},
access_controls={
"grant": grant_access_controls,
"block": block_access_controls,
},
)
}
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return conditional_access_policy
class User(BaseModel):
id: str
name: str
authentication_methods: List[Any] = []
@dataclass
@@ -221,7 +308,7 @@ class AuthorizationPolicy:
class GroupSetting:
name: Optional[str]
template_id: Optional[str]
settings: list[SettingValue]
settings: List[SettingValue]
class SecurityDefault(BaseModel):
@@ -232,10 +319,18 @@ class SecurityDefault(BaseModel):
class NamedLocation(BaseModel):
name: str
ip_ranges_addresses: list[str]
ip_ranges_addresses: List[str]
is_trusted: bool
class DirectoryRole(BaseModel):
id: str
members: list[User]
members: List[User]
class ConditionalAccessPolicy(BaseModel):
name: str
state: str
users: dict[str, List[str]]
target_resources: dict[str, List[str]]
access_controls: dict[str, List[str]]
@@ -0,0 +1,25 @@
"""
This module contains functions with user privileges in Azure.
"""
def is_privileged_user(user, privileged_roles) -> bool:
"""
Checks if a user is a privileged user.
Args:
user: An object representing the user to be checked.
privileged_roles: A dictionary containing privileged roles.
Returns:
A boolean value indicating whether the user is a privileged user.
"""
is_privileged = False
for role in privileged_roles.values():
if user in role.members:
is_privileged = True
break
return is_privileged
@@ -0,0 +1,233 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import DOMAIN
class Test_entra_conditional_access_policy_require_mfa_for_management_api:
def test_entra_no_subscriptions(self):
entra_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
entra_client.conditional_access_policy = {}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 0
def test_entra_tenant_no_policies(self):
entra_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
entra_client.conditional_access_policy = {DOMAIN: {}}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "Conditional Access Policy does not require MFA for management API."
)
def test_entra_tenant_policy_no_mfa(self):
entra_client = mock.MagicMock
policy_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
)
policy = ConditionalAccessPolicy(
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["797f4846-ba00-4fd7-ba43-dac1f8f63013"]},
access_controls={"grant": ["grant"]},
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "Conditional Access Policy does not require MFA for management API."
)
def test_entra_tenant_policy_mfa(self):
entra_client = mock.MagicMock
policy_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
)
policy = ConditionalAccessPolicy(
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["797f4846-ba00-4fd7-ba43-dac1f8f63013"]},
access_controls={"grant": ["grant", "MFA"]},
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Test Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy requires MFA for management API."
)
def test_entra_tenant_policy_mfa_disabled(self):
entra_client = mock.MagicMock
policy_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
)
policy = ConditionalAccessPolicy(
name="Test Policy",
state="disabled",
users={"include": ["All"]},
target_resources={"include": ["797f4846-ba00-4fd7-ba43-dac1f8f63013"]},
access_controls={"grant": ["grant", "MFA"]},
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "Conditional Access Policy does not require MFA for management API."
)
def test_entra_tenant_policy_mfa_no_target(self):
entra_client = mock.MagicMock
policy_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
)
policy = ConditionalAccessPolicy(
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": []},
access_controls={"grant": ["grant", "MFA"]},
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "Conditional Access Policy does not require MFA for management API."
)
def test_entra_tenant_policy_mfa_no_users(self):
entra_client = mock.MagicMock
policy_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_management_api.entra_conditional_access_policy_require_mfa_for_management_api import (
entra_conditional_access_policy_require_mfa_for_management_api,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
)
policy = ConditionalAccessPolicy(
name="Test Policy",
state="enabled",
users={"include": []},
target_resources={"include": ["797f4846-ba00-4fd7-ba43-dac1f8f63013"]},
access_controls={"grant": ["grant", "MFA"]},
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_require_mfa_for_management_api()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "Conditional Access Policy does not require MFA for management API."
)
@@ -0,0 +1,170 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import DOMAIN
class Test_entra_non_privileged_user_has_mfa:
def test_entra_no_tenants(self):
entra_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import (
entra_non_privileged_user_has_mfa,
)
entra_client.users = {}
check = entra_non_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_tenant_no_users(self):
entra_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import (
entra_non_privileged_user_has_mfa,
)
entra_client.users = {DOMAIN: {}}
check = entra_non_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_user_no_privileged_no_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import (
entra_non_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(id=str(uuid4()), members=[])
}
}
check = entra_non_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == "User 'foo' does not have MFA enabled."
assert result[0].resource_name == f"foo@{DOMAIN}"
assert result[0].resource_id == user_id
assert result[0].subscription == f"Tenant: {DOMAIN}"
def test_entra_user_no_privileged_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import (
entra_non_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo", "bar"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(id=str(uuid4()), members=[])
}
}
check = entra_non_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == "User 'foo' has MFA enabled."
assert result[0].resource_name == f"foo@{DOMAIN}"
assert result[0].resource_id == user_id
assert result[0].subscription == f"Tenant: {DOMAIN}"
def test_entra_user_privileged_no_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import (
entra_non_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(
id=str(uuid4()), members=[user]
)
}
}
check = entra_non_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_user_privileged_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import (
entra_non_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo", "bar"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(
id=str(uuid4()), members=[user]
)
}
}
check = entra_non_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
@@ -0,0 +1,170 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import DOMAIN
class Test_entra_privileged_user_has_mfa:
def test_entra_no_tenants(self):
entra_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa import (
entra_privileged_user_has_mfa,
)
entra_client.users = {}
check = entra_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_tenant_no_users(self):
entra_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa import (
entra_privileged_user_has_mfa,
)
entra_client.users = {DOMAIN: {}}
check = entra_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_user_no_privileged_no_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa import (
entra_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(id=str(uuid4()), members=[])
}
}
check = entra_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_user_no_privileged_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa import (
entra_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo", "bar"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(id=str(uuid4()), members=[])
}
}
check = entra_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 0
def test_entra_user_privileged_no_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa import (
entra_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(
id=str(uuid4()), members=[user]
)
}
}
check = entra_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == "User 'foo' does not have MFA enabled."
assert result[0].resource_name == f"foo@{DOMAIN}"
assert result[0].resource_id == user_id
assert result[0].subscription == f"Tenant: {DOMAIN}"
def test_entra_user_privileged_mfa(self):
entra_client = mock.MagicMock
user_id = str(uuid4())
with mock.patch(
"prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa.entra_client",
new=entra_client,
):
from prowler.providers.azure.services.entra.entra_privileged_user_has_mfa.entra_privileged_user_has_mfa import (
entra_privileged_user_has_mfa,
)
from prowler.providers.azure.services.entra.entra_service import (
DirectoryRole,
User,
)
user = User(id=user_id, name="foo", authentication_methods=["foo", "bar"])
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
entra_client.directory_roles = {
DOMAIN: {
"Global Administrator": DirectoryRole(
id=str(uuid4()), members=[user]
)
}
}
check = entra_privileged_user_has_mfa()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == "User 'foo' has MFA enabled."
assert result[0].resource_name == f"foo@{DOMAIN}"
assert result[0].resource_id == user_id
assert result[0].subscription == f"Tenant: {DOMAIN}"
@@ -2,6 +2,7 @@ from unittest.mock import patch
from prowler.providers.azure.services.entra.entra_service import (
AuthorizationPolicy,
ConditionalAccessPolicy,
DirectoryRole,
Entra,
GroupSetting,
@@ -78,6 +79,24 @@ async def mock_entra_get_directory_roles(_):
}
async def mock_entra_get_conditional_access_policy(_):
return {
DOMAIN: {
"id-1": ConditionalAccessPolicy(
id="id-1",
state="enabled",
name="Test",
users={"include": ["All"], "exclude": []},
target_resources={
"include": ["797f4846-ba00-4fd7-ba43-dac1f8f63013"],
"exclude": [],
},
access_controls={"grant": ["MFA"], "block": []},
)
}
}
@patch(
"prowler.providers.azure.services.entra.entra_service.Entra.__get_users__",
new=mock_entra_get_users,
@@ -102,6 +121,10 @@ async def mock_entra_get_directory_roles(_):
"prowler.providers.azure.services.entra.entra_service.Entra.__get_directory_roles__",
new=mock_entra_get_directory_roles,
)
@patch(
"prowler.providers.azure.services.entra.entra_service.Entra.__get_conditional_access_policy__",
new=mock_entra_get_conditional_access_policy,
)
class Test_Entra_Service:
def test__get_client__(self):
entra_client = Entra(set_mocked_azure_audit_info())
@@ -116,6 +139,10 @@ class Test_Entra_Service:
assert len(entra_client.users) == 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"
assert (
len(entra_client.users[DOMAIN]["user-1@tenant1.es"].authentication_methods)
== 0
)
def test__get_authorization_policy__(self):
entra_client = Entra(set_mocked_azure_audit_info())
@@ -159,3 +186,36 @@ class Test_Entra_Service:
len(entra_client.directory_roles[DOMAIN]["GlobalAdministrator"].members)
== 0
)
def test__get_conditional_access_policy__(self):
entra_client = Entra(set_mocked_azure_audit_info())
assert len(entra_client.conditional_access_policy) == 1
assert len(entra_client.conditional_access_policy[DOMAIN]) == 1
assert entra_client.conditional_access_policy[DOMAIN]["id-1"]
assert entra_client.conditional_access_policy[DOMAIN]["id-1"].name == "Test"
assert entra_client.conditional_access_policy[DOMAIN]["id-1"].state == "enabled"
assert entra_client.conditional_access_policy[DOMAIN]["id-1"].users[
"include"
] == ["All"]
assert (
entra_client.conditional_access_policy[DOMAIN]["id-1"].users["exclude"]
== []
)
assert entra_client.conditional_access_policy[DOMAIN]["id-1"].target_resources[
"include"
] == ["797f4846-ba00-4fd7-ba43-dac1f8f63013"]
assert (
entra_client.conditional_access_policy[DOMAIN]["id-1"].target_resources[
"exclude"
]
== []
)
assert entra_client.conditional_access_policy[DOMAIN]["id-1"].access_controls[
"grant"
] == ["MFA"]
assert (
entra_client.conditional_access_policy[DOMAIN]["id-1"].access_controls[
"block"
]
== []
)
@@ -0,0 +1,25 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.entra.entra_service import User
from prowler.providers.azure.services.entra.lib.user_privileges import (
is_privileged_user,
)
class Test_user_privileges_test:
def test_user_in_privileged_roles(self):
user_id = str(uuid4())
privileged_roles = {"admin": mock.MagicMock()}
privileged_roles["admin"].members = [User(id=user_id, name="user1")]
user = User(id=user_id, name="user1")
assert is_privileged_user(user, privileged_roles)
def test_user_not_in_privileged_roles(self):
user_id = str(uuid4())
privileged_roles = {"admin": mock.MagicMock()}
privileged_roles["admin"].members = [User(id=str(uuid4()), name="user2")]
user = User(id=user_id, name="user1")
assert not is_privileged_user(user, privileged_roles)