feat(Azure): Entra service with two checks (#3510)

This commit is contained in:
Rubén De la Torre Vico
2024-03-08 11:30:22 +00:00
committed by GitHub
parent c5dafcce43
commit 33884dbee5
14 changed files with 455 additions and 14 deletions

View File

@@ -151,7 +151,7 @@ class Azure_Provider:
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
asyncio.run(get_azure_identity())
asyncio.get_event_loop().run_until_complete(get_azure_identity())
# Managed identities only can be assigned resource, resource group and subscription scope permissions
elif managed_entity_auth:

View File

@@ -9,7 +9,7 @@ class AzureService:
audit_info: Azure_Audit_Info,
):
self.clients = self.__set_clients__(
audit_info.identity.subscriptions,
audit_info.identity,
audit_info.credentials,
service,
audit_info.azure_region_config,
@@ -20,10 +20,13 @@ class AzureService:
self.audit_config = audit_info.audit_config
def __set_clients__(self, subscriptions, credentials, service, region_config):
def __set_clients__(self, identity, credentials, service, region_config):
clients = {}
try:
for display_name, id in subscriptions.items():
if "GraphServiceClient" in str(service):
clients.update({identity.domain: service(credentials=credentials)})
else:
for display_name, id in identity.subscriptions.items():
clients.update(
{
display_name: service(

View File

@@ -0,0 +1,4 @@
from prowler.providers.azure.lib.audit_info.audit_info import azure_audit_info
from prowler.providers.azure.services.entra.entra_service import Entra
entra_client = Entra(azure_audit_info)

View File

@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "entra_policy_ensure_default_user_cannot_create_apps",
"CheckTitle": "Ensure That 'Users Can Register Applications' Is Set to 'No'",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "#microsoft.graph.authorizationPolicy",
"Description": "Require administrators or appropriately delegated users to register third-party applications.",
"Risk": "It is recommended to only allow an administrator to register custom-developed applications. This ensures that the application undergoes a formal security review and approval process prior to exposing Azure Active Directory data. Certain users like developers or other high-request users may also be delegated permissions to prevent them from waiting on an administrative user. Your organization should review your policies and decide your needs.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity-platform/how-applications-are-added#who-has-permission-to-add-applications-to-my-azure-ad-instance",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "1. From Azure Home select the Portal Menu 2. Select Azure Active Directory 3. Select Users 4. Select User settings 5. Ensure that Users can register applications is set to No",
"Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Enforcing this setting will create additional requests for approval that will need to be addressed by an administrator. If permissions are delegated, a user may approve a malevolent third party application, potentially giving it access to your data."
}

View File

@@ -0,0 +1,28 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.entra.entra_client import entra_client
class entra_policy_ensure_default_user_cannot_create_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"All from tenant '{tenant_domain}'"
report.resource_name = auth_policy.name
report.resource_id = auth_policy.id
report.status_extended = "App creation is not disabled for non-admin users."
if auth_policy.default_user_role_permissions and not getattr(
auth_policy.default_user_role_permissions,
"allowed_to_create_apps",
True,
):
report.status = "PASS"
report.status_extended = "App creation is disabled for non-admin users."
findings.append(report)
return findings

View File

@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "entra_policy_ensure_default_user_cannot_create_tenants",
"CheckTitle": "Ensure that 'Restrict non-admin users from creating tenants' is set to 'Yes'",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "#microsoft.graph.authorizationPolicy",
"Description": "Require administrators or appropriately delegated users to create new tenants.",
"Risk": "It is recommended to only allow an administrator to create new tenants. This prevent users from creating new Azure AD or Azure AD B2C tenants and ensures that only authorized users are able to do so.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/fundamentals/users-default-permissions",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "1. From Azure Home select the Portal Menu 2. Select Azure Active Directory 3. Select Users 4. Select User settings 5. Set 'Restrict non-admin users from creating' tenants to 'Yes'",
"Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#tenant-creator"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Enforcing this setting will ensure that only authorized users are able to create new tenants."
}

View File

@@ -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_policy_ensure_default_user_cannot_create_tenants(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"All from tenant '{tenant_domain}'"
report.resource_name = auth_policy.name
report.resource_id = auth_policy.id
report.status_extended = (
"Tenants creation is not disabled for non-admin users."
)
if auth_policy.default_user_role_permissions and not getattr(
auth_policy.default_user_role_permissions,
"allowed_to_create_tenants",
True,
):
report.status = "PASS"
report.status_extended = (
"Tenants creation is disabled for non-admin users."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,80 @@
import asyncio
from dataclasses import dataclass
from typing import Optional
from msgraph import GraphServiceClient
from msgraph.generated.models.default_user_role_permissions import (
DefaultUserRolePermissions,
)
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)
self.users = asyncio.get_event_loop().run_until_complete(self.__get_users__())
self.authorization_policy = asyncio.get_event_loop().run_until_complete(
self.__get_authorization_policy__()
)
async def __get_users__(self):
try:
users = {}
for tenant, client in self.clients.items():
users_list = await client.users.get()
users.update({tenant: {}})
for user in users_list.value:
users[tenant].update(
{
user.user_principal_name: User(
id=user.id, name=user.display_name
)
}
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return users
async def __get_authorization_policy__(self):
try:
authorization_policy = {}
for tenant, client in self.clients.items():
auth_policy = await client.policies.authorization_policy.get()
authorization_policy.update(
{
tenant: AuthorizationPolicy(
id=auth_policy.id,
name=auth_policy.display_name,
description=auth_policy.description,
default_user_role_permissions=getattr(
auth_policy, "default_user_role_permissions", None
),
)
}
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return authorization_policy
class User(BaseModel):
id: str
name: str
@dataclass
class AuthorizationPolicy:
id: str
name: str
description: str
default_user_role_permissions: Optional[DefaultUserRolePermissions]

View File

@@ -0,0 +1,90 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.entra.entra_service import AuthorizationPolicy
class Test_entra_policy_ensure_default_user_cannot_create_apps:
def test_entra_no_authorization_policy(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",
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,
)
check = entra_policy_ensure_default_user_cannot_create_apps()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "App creation is disabled for non-admin users."
)
assert result[0].resource_name == "Test"
assert result[0].resource_id == id
assert result[0].subscription == "All from tenant 'test.com'"
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",
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,
)
check = entra_policy_ensure_default_user_cannot_create_apps()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "App creation is not disabled for non-admin users."
)
assert result[0].resource_name == "Test"
assert result[0].resource_id == id
assert result[0].subscription == "All from tenant 'test.com'"

View File

@@ -0,0 +1,90 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.entra.entra_service import AuthorizationPolicy
class Test_entra_policy_ensure_default_user_cannot_create_tenants:
def test_entra_no_authorization_policy(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_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=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
),
)
}
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 == "FAIL"
assert (
result[0].status_extended
== "Tenants creation is not 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'"

View File

@@ -0,0 +1,54 @@
from unittest.mock import patch
from prowler.providers.azure.services.entra.entra_service import (
AuthorizationPolicy,
Entra,
User,
)
from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_audit_info
async def mock_entra_get_users(_):
return {
"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,
)
@patch(
"prowler.providers.azure.services.entra.entra_service.Entra.__get_users__",
new=mock_entra_get_users,
)
@patch(
"prowler.providers.azure.services.entra.entra_service.Entra.__get_authorization_policy__",
new=mock_entra_get_authorization_policy,
)
class Test_Entra_Service:
def test__get_client__(self):
entra_client = Entra(set_mocked_azure_audit_info())
assert entra_client.clients[DOMAIN].__class__.__name__ == "GraphServiceClient"
def test__get_subscriptions__(self):
entra_client = Entra(set_mocked_azure_audit_info())
assert entra_client.subscriptions.__class__.__name__ == "dict"
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"
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