diff --git a/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/__init__.py b/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on.metadata.json b/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on.metadata.json new file mode 100644 index 00000000..9f4d1f43 --- /dev/null +++ b/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "defender_ensure_iot_hub_defender_is_on", + "CheckTitle": "Ensure That Microsoft Defender for IoT Hub Is Set To 'On'", + "CheckType": [], + "ServiceName": "defender", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "DefenderIoT", + "Description": "Microsoft Defender for IoT acts as a central security hub for IoT devices within your organization.", + "Risk": "IoT devices are very rarely patched and can be potential attack vectors for enterprise networks. Updating their network configuration to use a central security hub allows for detection of these breaches.", + "RelatedUrl": "https://azure.microsoft.com/en-us/services/iot-defender/#overview", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Go to IoT Hub. 2. Select a IoT Hub to validate. 3. Select Overview in Defender for IoT. 4. Click on Secure your IoT solution, and complete the onboarding.", + "Url": "https://learn.microsoft.com/en-us/azure/defender-for-iot/device-builders/quickstart-onboard-iot-hub" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enabling Microsoft Defender for IoT will incur additional charges dependent on the level of usage." +} diff --git a/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on.py b/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on.py new file mode 100644 index 00000000..96efa542 --- /dev/null +++ b/prowler/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on.py @@ -0,0 +1,41 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.defender.defender_client import defender_client + + +class defender_ensure_iot_hub_defender_is_on(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for ( + subscription_name, + iot_security_solutions, + ) in defender_client.iot_security_solutions.items(): + + if not iot_security_solutions: + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = subscription_name + report.resource_name = "IoT Hub Defender" + report.resource_id = "IoT Hub Defender" + report.status_extended = f"No IoT Security Solutions found in the subscription {subscription_name}." + findings.append(report) + continue + + for ( + iot_security_solution_name, + iot_security_solution, + ) in iot_security_solutions.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "PASS" + report.subscription = subscription_name + report.resource_name = iot_security_solution_name + report.resource_id = iot_security_solution.resource_id + report.status_extended = f"The security solution {iot_security_solution_name} is enabled in susbscription {subscription_name}." + + if iot_security_solution.status != "Enabled": + report.status = "FAIL" + report.status_extended = f"The security solution {iot_security_solution_name} is disabled in susbscription {subscription_name}" + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/defender/defender_service.py b/prowler/providers/azure/services/defender/defender_service.py index 8a24e5dd..442a5f81 100644 --- a/prowler/providers/azure/services/defender/defender_service.py +++ b/prowler/providers/azure/services/defender/defender_service.py @@ -17,6 +17,7 @@ class Defender(AzureService): self.assessments = self.__get_assessments__() self.settings = self.__get_settings__() self.security_contacts = self.__get_security_contacts__() + self.iot_security_solutions = self.__get_iot_security_solutions__() def __get_pricings__(self): logger.info("Defender - Getting pricings...") @@ -142,6 +143,30 @@ class Defender(AzureService): ) return security_contacts + def __get_iot_security_solutions__(self): + logger.info("Defender - Getting IoT Security Solutions...") + iot_security_solutions = {} + for subscription_name, client in self.clients.items(): + try: + iot_security_solutions_list = ( + client.iot_security_solution.list_by_subscription() + ) + iot_security_solutions.update({subscription_name: {}}) + for iot_security_solution in iot_security_solutions_list: + iot_security_solutions[subscription_name].update( + { + iot_security_solution.name: IoTSecuritySolution( + resource_id=iot_security_solution.id, + status=iot_security_solution.status, + ) + } + ) + except Exception as error: + logger.error( + f"Subscription name: {subscription_name} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return iot_security_solutions + class Pricing(BaseModel): resource_id: str @@ -177,3 +202,8 @@ class SecurityContacts(BaseModel): alert_notifications_state: str notified_roles: list[str] notified_roles_state: str + + +class IoTSecuritySolution(BaseModel): + resource_id: str + status: str diff --git a/tests/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on_test.py b/tests/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on_test.py new file mode 100644 index 00000000..68d96b51 --- /dev/null +++ b/tests/providers/azure/services/defender/defender_ensure_iot_hub_defender_is_on/defender_ensure_iot_hub_defender_is_on_test.py @@ -0,0 +1,153 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.defender.defender_service import ( + IoTSecuritySolution, +) +from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION + + +class Test_defender_ensure_iot_hub_defender_is_on: + def test_defender_no_subscriptions(self): + defender_client = mock.MagicMock + defender_client.iot_security_solutions = {} + + with mock.patch( + "prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on.defender_client", + new=defender_client, + ): + from prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on import ( + defender_ensure_iot_hub_defender_is_on, + ) + + check = defender_ensure_iot_hub_defender_is_on() + result = check.execute() + assert len(result) == 0 + + def test_defender_no_iot_hub_solutions(self): + defender_client = mock.MagicMock + defender_client.iot_security_solutions = {AZURE_SUBSCRIPTION: {}} + + with mock.patch( + "prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on.defender_client", + new=defender_client, + ): + from prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on import ( + defender_ensure_iot_hub_defender_is_on, + ) + + check = defender_ensure_iot_hub_defender_is_on() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"No IoT Security Solutions found in the subscription {AZURE_SUBSCRIPTION}." + ) + assert result[0].resource_name == "IoT Hub Defender" + assert result[0].resource_id == "IoT Hub Defender" + + def test_defender_iot_hub_solution_disabled(self): + resource_id = str(uuid4()) + defender_client = mock.MagicMock + defender_client.iot_security_solutions = { + AZURE_SUBSCRIPTION: { + "iot_sec_solution": IoTSecuritySolution( + resource_id=resource_id, status="Disabled" + ) + } + } + + with mock.patch( + "prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on.defender_client", + new=defender_client, + ): + from prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on import ( + defender_ensure_iot_hub_defender_is_on, + ) + + check = defender_ensure_iot_hub_defender_is_on() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"The security solution iot_sec_solution is disabled in susbscription {AZURE_SUBSCRIPTION}" + ) + assert result[0].resource_name == "iot_sec_solution" + assert result[0].resource_id == resource_id + + def test_defender_iot_hub_solution_enabled(self): + resource_id = str(uuid4()) + defender_client = mock.MagicMock + defender_client.iot_security_solutions = { + AZURE_SUBSCRIPTION: { + "iot_sec_solution": IoTSecuritySolution( + resource_id=resource_id, status="Enabled" + ) + } + } + + with mock.patch( + "prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on.defender_client", + new=defender_client, + ): + from prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on import ( + defender_ensure_iot_hub_defender_is_on, + ) + + check = defender_ensure_iot_hub_defender_is_on() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"The security solution iot_sec_solution is enabled in susbscription {AZURE_SUBSCRIPTION}." + ) + assert result[0].resource_name == "iot_sec_solution" + assert result[0].resource_id == resource_id + assert result[0].subscription == AZURE_SUBSCRIPTION + + def test_defender_multiple_iot_hub_solution_enabled_and_disabled(self): + resource_id_enabled = str(uuid4()) + resource_id_disabled = str(uuid4()) + defender_client = mock.MagicMock + defender_client.iot_security_solutions = { + AZURE_SUBSCRIPTION: { + "iot_sec_solution_enabled": IoTSecuritySolution( + resource_id=resource_id_enabled, status="Enabled" + ), + "iot_sec_solution_disabled": IoTSecuritySolution( + resource_id=resource_id_disabled, status="Disabled" + ), + } + } + + with mock.patch( + "prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on.defender_client", + new=defender_client, + ): + from prowler.providers.azure.services.defender.defender_ensure_iot_hub_defender_is_on.defender_ensure_iot_hub_defender_is_on import ( + defender_ensure_iot_hub_defender_is_on, + ) + + check = defender_ensure_iot_hub_defender_is_on() + result = check.execute() + assert len(result) == 2 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"The security solution iot_sec_solution_enabled is enabled in susbscription {AZURE_SUBSCRIPTION}." + ) + assert result[0].resource_name == "iot_sec_solution_enabled" + assert result[0].resource_id == resource_id_enabled + assert result[0].subscription == AZURE_SUBSCRIPTION + + assert result[1].status == "FAIL" + assert ( + result[1].status_extended + == f"The security solution iot_sec_solution_disabled is disabled in susbscription {AZURE_SUBSCRIPTION}" + ) + assert result[1].resource_name == "iot_sec_solution_disabled" + assert result[1].resource_id == resource_id_disabled + assert result[1].subscription == AZURE_SUBSCRIPTION diff --git a/tests/providers/azure/services/defender/defender_service_test.py b/tests/providers/azure/services/defender/defender_service_test.py index 86213f56..ba8d5757 100644 --- a/tests/providers/azure/services/defender/defender_service_test.py +++ b/tests/providers/azure/services/defender/defender_service_test.py @@ -5,6 +5,7 @@ from prowler.providers.azure.services.defender.defender_service import ( Assesment, AutoProvisioningSetting, Defender, + IoTSecuritySolution, Pricing, SecurityContacts, Setting, @@ -81,6 +82,17 @@ def mock_defender_get_settings(_): } +def mock_defender_get_iot_security_solutions(_): + return { + AZURE_SUBSCRIPTION: { + "iot_sec_solution": IoTSecuritySolution( + resource_id="/subscriptions/resource_id", + status="Enabled", + ) + } + } + + @patch( "prowler.providers.azure.services.defender.defender_service.Defender.__get_pricings__", new=mock_defender_get_pricings, @@ -101,6 +113,10 @@ def mock_defender_get_settings(_): "prowler.providers.azure.services.defender.defender_service.Defender.__get_security_contacts__", new=mock_defender_get_security_contacts, ) +@patch( + "prowler.providers.azure.services.defender.defender_service.Defender.__get_iot_security_solutions__", + new=mock_defender_get_iot_security_solutions, +) class Test_Defender_Service: def test__get_client__(self): defender = Defender(set_mocked_azure_audit_info()) @@ -221,3 +237,19 @@ class Test_Defender_Service: ].notified_roles_state == "On" ) + + def test__get_iot_security_solutions__(self): + defender = Defender(set_mocked_azure_audit_info()) + assert len(defender.iot_security_solutions) == 1 + assert ( + defender.iot_security_solutions[AZURE_SUBSCRIPTION][ + "iot_sec_solution" + ].resource_id + == "/subscriptions/resource_id" + ) + assert ( + defender.iot_security_solutions[AZURE_SUBSCRIPTION][ + "iot_sec_solution" + ].status + == "Enabled" + )