diff --git a/poetry.lock b/poetry.lock index 35409afe..620b5522 100644 --- a/poetry.lock +++ b/poetry.lock @@ -485,6 +485,22 @@ azure-common = ">=1.1,<2.0" azure-mgmt-core = ">=1.3.0,<2.0.0" msrest = ">=0.6.21" +[[package]] +name = "azure-mgmt-resource" +version = "23.0.1" +description = "Microsoft Azure Resource Management Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-resource-23.0.1.zip", hash = "sha256:c2ba6cfd99df95f55f36eadc4245e3dc713257302a1fd0277756d94bd8cb28e0"}, + {file = "azure_mgmt_resource-23.0.1-py3-none-any.whl", hash = "sha256:f185eec72bbc39f42bcb83ae6f1bad744f0e3f20a12d9b2b3e70d16c74ad9cc0"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.2,<2.0.0" +isodate = ">=0.6.1,<1.0.0" + [[package]] name = "azure-mgmt-security" version = "6.0.0" @@ -4393,4 +4409,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "ab8c8f47bee7e9efdfcb3c736ccd97ea74cd8c601168541e64e3c2c486b9ae58" +content-hash = "266ed59973207ca74ea393dfeed3ce27bb4b7244214cefb7260758803ca938fb" diff --git a/prowler/providers/azure/services/policy/__init__.py b/prowler/providers/azure/services/policy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/azure/services/policy/policy_client.py b/prowler/providers/azure/services/policy/policy_client.py new file mode 100644 index 00000000..52979212 --- /dev/null +++ b/prowler/providers/azure/services/policy/policy_client.py @@ -0,0 +1,4 @@ +from prowler.providers.azure.lib.audit_info.audit_info import azure_audit_info +from prowler.providers.azure.services.policy.policy_service import Policy + +policy_client = Policy(azure_audit_info) diff --git a/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/__init__.py b/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled.metadata.json b/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled.metadata.json new file mode 100644 index 00000000..21d1a0eb --- /dev/null +++ b/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "policy_ensure_asc_enforcement_enabled", + "CheckTitle": "Ensure Any of the ASC Default Policy Settings are Not Set to 'Disabled'", + "CheckType": [], + "ServiceName": "policy", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Microsoft.Authorization/policyAssignments", + "Description": "None of the settings offered by ASC Default policy should be set to effect Disabled.", + "Risk": "A security policy defines the desired configuration of your workloads and helps ensure compliance with company or regulatory security requirements. ASC Default policy is associated with every subscription by default. ASC default policy assignment is a set of security recommendations based on best practices. Enabling recommendations in ASC default policy ensures that Azure security center provides the ability to monitor all of the supported recommendations and optionally allow automated action for a few of the supported recommendations.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/defender-for-cloud/security-policy-concept", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. From Azure Home select the Portal Menu 2. Select Policy 3. Select ASC Default for each subscription 4. Click on 'view Assignment' 5. Click on 'Edit assignment' 6. Ensure Policy Enforcement is Enabled 7. Click 'Review + Save'", + "Url": "https://learn.microsoft.com/en-us/azure/defender-for-cloud/implement-security-recommendations" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled.py b/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled.py new file mode 100644 index 00000000..92295f0a --- /dev/null +++ b/prowler/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled.py @@ -0,0 +1,24 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.policy.policy_client import policy_client + + +class policy_ensure_asc_enforcement_enabled(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for subscription_name, policies in policy_client.policy_assigments.items(): + if "SecurityCenterBuiltIn" in policies: + report = Check_Report_Azure(self.metadata()) + report.status = "PASS" + report.subscription = subscription_name + report.resource_name = "SecurityCenterBuiltIn" + report.resource_id = policies["SecurityCenterBuiltIn"].id + report.status_extended = f"Policy assigment '{policies['SecurityCenterBuiltIn'].id}' is configured with enforcement mode '{policies['SecurityCenterBuiltIn'].enforcement_mode}'." + + if policies["SecurityCenterBuiltIn"].enforcement_mode != "Default": + report.status = "FAIL" + report.status_extended = f"Policy assigment '{policies['SecurityCenterBuiltIn'].id}' is not configured with enforcement mode Default." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/policy/policy_service.py b/prowler/providers/azure/services/policy/policy_service.py new file mode 100644 index 00000000..af3e4fe6 --- /dev/null +++ b/prowler/providers/azure/services/policy/policy_service.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass + +from azure.mgmt.resource.policy import PolicyClient + +from prowler.lib.logger import logger +from prowler.providers.azure.lib.audit_info.models import Azure_Audit_Info +from prowler.providers.azure.lib.service.service import AzureService + + +########################## Policy +class Policy(AzureService): + def __init__(self, audit_info: Azure_Audit_Info): + super().__init__(PolicyClient, audit_info) + self.policy_assigments = self.__get_policy_assigments__() + + def __get_policy_assigments__(self): + logger.info("Policy - Getting policy assigments...") + policy_assigments = {} + + for subscription_name, client in self.clients.items(): + try: + policy_assigments_list = client.policy_assignments.list() + policy_assigments.update({subscription_name: {}}) + + for policy_assigment in policy_assigments_list: + policy_assigments[subscription_name].update( + { + policy_assigment.name: PolicyAssigment( + id=policy_assigment.id, + enforcement_mode=policy_assigment.enforcement_mode, + ) + } + ) + except Exception as error: + logger.error( + f"Subscription name: {subscription_name} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return policy_assigments + + +@dataclass +class PolicyAssigment: + id: str + enforcement_mode: str diff --git a/pyproject.toml b/pyproject.toml index 13934fd4..be2c74c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,10 +33,11 @@ azure-mgmt-applicationinsights = "4.0.0" azure-mgmt-authorization = "4.0.0" azure-mgmt-compute = "30.5.0" azure-mgmt-cosmosdb = "9.4.0" -azure-mgmt-monitor = "6.0.2" azure-mgmt-keyvault = "10.3.0" +azure-mgmt-monitor = "6.0.2" azure-mgmt-network = "25.3.0" azure-mgmt-rdbms = "10.1.0" +azure-mgmt-resource = "23.0.1" azure-mgmt-security = "6.0.0" azure-mgmt-sql = "3.0.1" azure-mgmt-storage = "21.1.0" diff --git a/tests/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled_test.py b/tests/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled_test.py new file mode 100644 index 00000000..398ff8b4 --- /dev/null +++ b/tests/providers/azure/services/policy/policy_ensure_asc_enforcement_enabled/policy_ensure_asc_enforcement_enabled_test.py @@ -0,0 +1,122 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.policy.policy_service import PolicyAssigment +from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION + + +class Test_policy_ensure_asc_enforcement_enabled: + def test_policy_no_subscriptions(self): + policy_client = mock.MagicMock + policy_client.policy_assigments = {} + + with mock.patch( + "prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled.policy_client", + new=policy_client, + ): + from prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled import ( + policy_ensure_asc_enforcement_enabled, + ) + + check = policy_ensure_asc_enforcement_enabled() + result = check.execute() + assert len(result) == 0 + + def test_policy_subscription_empty(self): + policy_client = mock.MagicMock + policy_client.policy_assigments = {AZURE_SUBSCRIPTION: {}} + + with mock.patch( + "prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled.policy_client", + new=policy_client, + ): + from prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled import ( + policy_ensure_asc_enforcement_enabled, + ) + + check = policy_ensure_asc_enforcement_enabled() + result = check.execute() + assert len(result) == 0 + + def test_policy_subscription_no_asc(self): + policy_client = mock.MagicMock + resource_id = uuid4() + policy_client.policy_assigments = { + AZURE_SUBSCRIPTION: { + "policy-1": PolicyAssigment(id=resource_id, enforcement_mode="Default") + } + } + + with mock.patch( + "prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled.policy_client", + new=policy_client, + ): + from prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled import ( + policy_ensure_asc_enforcement_enabled, + ) + + check = policy_ensure_asc_enforcement_enabled() + result = check.execute() + assert len(result) == 0 + + def test_policy_subscription_asc_default(self): + policy_client = mock.MagicMock + resource_id = uuid4() + policy_client.policy_assigments = { + AZURE_SUBSCRIPTION: { + "SecurityCenterBuiltIn": PolicyAssigment( + id=resource_id, enforcement_mode="Default" + ) + } + } + + with mock.patch( + "prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled.policy_client", + new=policy_client, + ): + from prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled import ( + policy_ensure_asc_enforcement_enabled, + ) + + check = policy_ensure_asc_enforcement_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Policy assigment '{resource_id}' is configured with enforcement mode 'Default'." + ) + assert result[0].resource_id == resource_id + assert result[0].resource_name == "SecurityCenterBuiltIn" + assert result[0].subscription == AZURE_SUBSCRIPTION + + def test_policy_subscription_asc_not_default(self): + policy_client = mock.MagicMock + resource_id = uuid4() + policy_client.policy_assigments = { + AZURE_SUBSCRIPTION: { + "SecurityCenterBuiltIn": PolicyAssigment( + id=resource_id, enforcement_mode="DoNotEnforce" + ) + } + } + + with mock.patch( + "prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled.policy_client", + new=policy_client, + ): + from prowler.providers.azure.services.policy.policy_ensure_asc_enforcement_enabled.policy_ensure_asc_enforcement_enabled import ( + policy_ensure_asc_enforcement_enabled, + ) + + check = policy_ensure_asc_enforcement_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Policy assigment '{resource_id}' is not configured with enforcement mode Default." + ) + assert result[0].resource_id == resource_id + assert result[0].resource_name == "SecurityCenterBuiltIn" + assert result[0].subscription == AZURE_SUBSCRIPTION diff --git a/tests/providers/azure/services/policy/policy_service_test.py b/tests/providers/azure/services/policy/policy_service_test.py new file mode 100644 index 00000000..35266af4 --- /dev/null +++ b/tests/providers/azure/services/policy/policy_service_test.py @@ -0,0 +1,46 @@ +from unittest.mock import patch + +from prowler.providers.azure.services.policy.policy_service import ( + Policy, + PolicyAssigment, +) +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION, + set_mocked_azure_audit_info, +) + + +def mock_policy_assigments(_): + return { + AZURE_SUBSCRIPTION: { + "policy-1": PolicyAssigment(id="id-1", enforcement_mode="Default") + } + } + + +@patch( + "prowler.providers.azure.services.policy.policy_service.Policy.__get_policy_assigments__", + new=mock_policy_assigments, +) +class Test_AppInsights_Service: + def test__get_client__(self): + policy = Policy(set_mocked_azure_audit_info()) + assert policy.clients[AZURE_SUBSCRIPTION].__class__.__name__ == "PolicyClient" + + def test__get_subscriptions__(self): + policy = Policy(set_mocked_azure_audit_info()) + assert policy.subscriptions.__class__.__name__ == "dict" + + def test__get_policy_assigments__(self): + policy = Policy(set_mocked_azure_audit_info()) + assert policy.policy_assigments.__class__.__name__ == "dict" + assert policy.policy_assigments[AZURE_SUBSCRIPTION].__class__.__name__ == "dict" + assert ( + policy.policy_assigments[AZURE_SUBSCRIPTION]["policy-1"].__class__.__name__ + == "PolicyAssigment" + ) + assert policy.policy_assigments[AZURE_SUBSCRIPTION]["policy-1"].id == "id-1" + assert ( + policy.policy_assigments[AZURE_SUBSCRIPTION]["policy-1"].enforcement_mode + == "Default" + )