From a09be4c0babd7e4dde77119cd2179537fd8af626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 15 Jul 2025 16:37:43 +0200 Subject: [PATCH] chore(azure/defender): get security contacts from API REST (#8241) --- prowler/CHANGELOG.md | 1 + ...mail_configured_with_a_security_contact.py | 24 +- ...r_ensure_notify_alerts_severity_is_high.py | 16 +- ...defender_ensure_notify_emails_to_owners.py | 21 +- .../services/defender/defender_service.py | 156 ++++++---- ...configured_with_a_security_contact_test.py | 275 ++---------------- ...ure_notify_alerts_severity_is_high_test.py | 131 +++------ ...der_ensure_notify_emails_to_owners_test.py | 168 +++-------- .../defender/defender_service_test.py | 79 ++--- 9 files changed, 274 insertions(+), 597 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f117dc8bbd..e00a3558f5 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add `test_connection` method to GitHub provider [(#8248)](https://github.com/prowler-cloud/prowler/pull/8248) ### Changed +- Refactor the Azure Defender get security contact configuration method to use the API REST endpoint instead of the SDK [(#8241)](https://github.com/prowler-cloud/prowler/pull/8241) ### Fixed - Title & description wording for `iam_user_accesskey_unused` check for AWS provider [(#8233)](https://github.com/prowler-cloud/prowler/pull/8233) diff --git a/prowler/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact.py b/prowler/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact.py index 2e7fa7c9d3..8c69f4437a 100644 --- a/prowler/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact.py +++ b/prowler/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact.py @@ -1,5 +1,3 @@ -import re - from prowler.lib.check.models import Check, Check_Report_Azure from prowler.providers.azure.services.defender.defender_client import defender_client @@ -10,21 +8,17 @@ class defender_additional_email_configured_with_a_security_contact(Check): for ( subscription_name, - security_contacts, - ) in defender_client.security_contacts.items(): - for contact in security_contacts.values(): - report = Check_Report_Azure(metadata=self.metadata(), resource=contact) - report.status = "PASS" + security_contact_configurations, + ) in defender_client.security_contact_configurations.items(): + for contact_configuration in security_contact_configurations.values(): + report = Check_Report_Azure( + metadata=self.metadata(), resource=contact_configuration + ) report.subscription = subscription_name - report.status_extended = f"There is another correct email configured for subscription {subscription_name}." - emails = contact.emails.split(";") - - for email in emails: - if re.fullmatch( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", email - ): - break + if len(contact_configuration.emails) > 0: + report.status = "PASS" + report.status_extended = f"There is another correct email configured for subscription {subscription_name}." else: report.status = "FAIL" report.status_extended = f"There is not another correct email configured for subscription {subscription_name}." diff --git a/prowler/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high.py b/prowler/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high.py index 34eaab9624..e0ad128e05 100644 --- a/prowler/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high.py +++ b/prowler/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high.py @@ -8,20 +8,22 @@ class defender_ensure_notify_alerts_severity_is_high(Check): for ( subscription_name, - security_contacts, - ) in defender_client.security_contacts.items(): - for contact in security_contacts.values(): - report = Check_Report_Azure(metadata=self.metadata(), resource=contact) + security_contact_configurations, + ) in defender_client.security_contact_configurations.items(): + for contact_configuration in security_contact_configurations.values(): + report = Check_Report_Azure( + metadata=self.metadata(), resource=contact_configuration + ) report.subscription = subscription_name report.status = "FAIL" report.status_extended = f"Notifications are not enabled for alerts with a minimum severity of high or lower in subscription {subscription_name}." if ( - contact.alert_notifications_minimal_severity != "Critical" - and contact.alert_notifications_minimal_severity != "" + contact_configuration.alert_minimal_severity + and contact_configuration.alert_minimal_severity != "Critical" ): report.status = "PASS" - report.status_extended = f"Notifications are enabled for alerts with a minimum severity of high or lower ({contact.alert_notifications_minimal_severity}) in subscription {subscription_name}." + report.status_extended = f"Notifications are enabled for alerts with a minimum severity of high or lower ({contact_configuration.alert_minimal_severity}) in subscription {subscription_name}." findings.append(report) diff --git a/prowler/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners.py b/prowler/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners.py index be18d2bfb1..ce041464b4 100644 --- a/prowler/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners.py +++ b/prowler/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners.py @@ -8,19 +8,20 @@ class defender_ensure_notify_emails_to_owners(Check): for ( subscription_name, - security_contacts, - ) in defender_client.security_contacts.items(): - for contact in security_contacts.values(): - report = Check_Report_Azure(metadata=self.metadata(), resource=contact) - report.subscription = subscription_name - report.status = "PASS" - report.status_extended = ( - f"The Owner role is notified for subscription {subscription_name}." + security_contact_configurations, + ) in defender_client.security_contact_configurations.items(): + for contact_configuration in security_contact_configurations.values(): + report = Check_Report_Azure( + metadata=self.metadata(), resource=contact_configuration ) + report.subscription = subscription_name if ( - contact.notified_roles_state != "On" - or "Owner" not in contact.notified_roles + contact_configuration.notifications_by_role.state + and "Owner" in contact_configuration.notifications_by_role.roles ): + report.status = "PASS" + report.status_extended = f"The Owner role is notified for subscription {subscription_name}." + else: report.status = "FAIL" report.status_extended = f"The Owner role is not notified for subscription {subscription_name}." diff --git a/prowler/providers/azure/services/defender/defender_service.py b/prowler/providers/azure/services/defender/defender_service.py index 2f6eaeb05d..f39dfdf484 100644 --- a/prowler/providers/azure/services/defender/defender_service.py +++ b/prowler/providers/azure/services/defender/defender_service.py @@ -1,11 +1,8 @@ from datetime import timedelta -from typing import Dict +from typing import Dict, Optional -from azure.core.exceptions import ( - ClientAuthenticationError, - HttpResponseError, - ResourceNotFoundError, -) +import requests +from azure.core.exceptions import ClientAuthenticationError, ResourceNotFoundError from azure.mgmt.security import SecurityCenter from pydantic.v1 import BaseModel @@ -22,7 +19,11 @@ class Defender(AzureService): self.auto_provisioning_settings = self._get_auto_provisioning_settings() self.assessments = self._get_assessments() self.settings = self._get_settings() - self.security_contacts = self._get_security_contacts() + self.security_contact_configurations = self._get_security_contacts( + token=provider.session.get_token( + "https://management.azure.com/.default" + ).token + ) self.iot_security_solutions = self._get_iot_security_solutions() def _get_pricings(self): @@ -149,48 +150,70 @@ class Defender(AzureService): ) return settings - def _get_security_contacts(self): + def _get_security_contacts(self, token: str) -> dict[str, dict]: + """ + Get all security contacts configuration for all subscriptions. + + Args: + token: The authentication token to make the request. + + Returns: + A dictionary of security contacts for all subscriptions. + """ logger.info("Defender - Getting security contacts...") security_contacts = {} - for subscription_name, client in self.clients.items(): + for subscription_name, subscription_id in self.subscriptions.items(): try: - security_contacts.update({subscription_name: {}}) - # TODO: List all security contacts. For now, the list method is not working. - security_contact_default = client.security_contacts.get("default") - security_contacts[subscription_name].update( - { - security_contact_default.name: SecurityContacts( - resource_id=security_contact_default.id, - name=getattr(security_contact_default, "name", "default") - or "default", - emails=security_contact_default.emails, - phone=security_contact_default.phone, - alert_notifications_minimal_severity=security_contact_default.alert_notifications.minimal_severity, - alert_notifications_state=security_contact_default.alert_notifications.state, - notified_roles=security_contact_default.notifications_by_role.roles, - notified_roles_state=security_contact_default.notifications_by_role.state, - ) - } - ) - except HttpResponseError as error: - if error.status_code == 404: - security_contacts[subscription_name].update( - { - "default": SecurityContacts( - resource_id=f"/subscriptions/{self.subscriptions[subscription_name]}/providers/Microsoft.Security/securityContacts/default", - name="default", - emails="", - phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", - ) - } + url = f"https://management.azure.com/subscriptions/{subscription_id}/providers/Microsoft.Security/securityContacts?api-version=2023-12-01-preview" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + response = requests.get(url, headers=headers) + response.raise_for_status() + contact_configurations = response.json().get("value", []) + security_contacts[subscription_name] = {} + for contact_configuration in contact_configurations: + props = contact_configuration.get("properties", {}) + + # Map notificationsByRole.state from "On"/"Off" to boolean + notifications_by_role_state = props.get( + "notificationsByRole", {} + ).get("state", "Off") + notifications_by_role_state_bool = ( + notifications_by_role_state.lower() == "on" ) - else: - logger.error( - f"Subscription name: {subscription_name} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + notifications_by_role_roles = props.get( + "notificationsByRole", {} + ).get("roles", []) + + # Extract minimalRiskLevel and minimalSeverity from notificationsSources + attack_path_minimal_risk_level = None + alert_minimal_severity = None + for source in props.get("notificationsSources", []): + if source.get("sourceType") == "AttackPath": + value = source.get("minimalRiskLevel") + if value is not None: + attack_path_minimal_risk_level = value + elif source.get("sourceType") == "Alert": + value = source.get("minimalSeverity") + if value is not None: + alert_minimal_severity = value + + security_contacts[subscription_name][ + contact_configuration.get("name", "default") + ] = SecurityContactConfiguration( + id=contact_configuration.get("id", ""), + name=contact_configuration.get("name", "default"), + enabled=props.get("isEnabled", False), + emails=props.get("emails", "").split(";"), + phone=props.get("phone", ""), + notifications_by_role=NotificationsByRole( + state=notifications_by_role_state_bool, + roles=notifications_by_role_roles, + ), + attack_path_minimal_risk_level=attack_path_minimal_risk_level, + alert_minimal_severity=alert_minimal_severity, ) except Exception as error: logger.error( @@ -252,15 +275,42 @@ class Setting(BaseModel): enabled: bool -class SecurityContacts(BaseModel): - resource_id: str +class NotificationsByRole(BaseModel): + """ + Defines whether to send email notifications from Microsoft Defender for Cloud to persons with specific RBAC roles on the subscription. + + Attributes: + state: Whether notifications by role are enabled. + roles: List of Azure roles (e.g., 'Owner', 'Admin') to be notified. + """ + + state: bool + roles: list[str] + + +class SecurityContactConfiguration(BaseModel): + """ + Represents the configuration of an Azure Security Center security contact. + + Attributes: + id: The unique resource ID of the security contact. + name: The name of the security contact (usually 'default'). + enabled: Whether the security contact is enabled. If enabled, the security contact will receive notifications, otherwise it will not. + emails: List of email addresses to notify. + phone: Contact phone number. + notifications_by_role: Defines whether to send email notifications from Microsoft Defender for Cloud to persons with specific RBAC roles on the subscription. + attack_path_minimal_risk_level: Minimal risk level for Attack Path notifications (e.g., 'Critical'). + alert_minimal_severity: Minimal severity for Alert notifications (e.g., 'Medium'). + """ + + id: str name: str - emails: str - phone: str - alert_notifications_minimal_severity: str - alert_notifications_state: str - notified_roles: list[str] - notified_roles_state: str + enabled: bool + emails: list[str] + phone: Optional[str] = None + notifications_by_role: NotificationsByRole + attack_path_minimal_risk_level: Optional[str] = None + alert_minimal_severity: Optional[str] = None class IoTSecuritySolution(BaseModel): diff --git a/tests/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact_test.py b/tests/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact_test.py index 3ef2114d4c..752c8a6641 100644 --- a/tests/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact_test.py +++ b/tests/providers/azure/services/defender/defender_additional_email_configured_with_a_security_contact/defender_additional_email_configured_with_a_security_contact_test.py @@ -1,7 +1,10 @@ from unittest import mock from uuid import uuid4 -from prowler.providers.azure.services.defender.defender_service import SecurityContacts +from prowler.providers.azure.services.defender.defender_service import ( + NotificationsByRole, + SecurityContactConfiguration, +) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, set_mocked_azure_provider, @@ -10,8 +13,8 @@ from tests.providers.azure.azure_fixtures import ( class Test_defender_additional_email_configured_with_a_security_contact: def test_defender_no_subscriptions(self): - defender_client = mock.MagicMock - defender_client.security_contacts = {} + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = {} with ( mock.patch( @@ -33,18 +36,20 @@ class Test_defender_additional_email_configured_with_a_security_contact: def test_defender_no_additional_emails(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, + resource_id: SecurityContactConfiguration( + id=resource_id, name="default", - emails="", + enabled=True, + emails=[], phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Contributor"] + ), + alert_minimal_severity=None, + attack_path_minimal_risk_level=None, ) } } @@ -75,108 +80,22 @@ class Test_defender_additional_email_configured_with_a_security_contact: assert result[0].resource_name == "default" assert result[0].resource_id == resource_id - def test_defender_additional_email_bad_format(self): + def test_defender_additional_email_configured(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, + resource_id: SecurityContactConfiguration( + id=resource_id, name="default", - emails="bad_email", + enabled=True, + emails=["test@test.com"], phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", - ) - } - } - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact import ( - defender_additional_email_configured_with_a_security_contact, - ) - - check = defender_additional_email_configured_with_a_security_contact() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"There is not another correct email configured for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert result[0].resource_id == resource_id - - def test_defender_additional_email_bad_separator(self): - resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, - name="default", - emails="test@test.es, test@test.email.com", - phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", - ) - } - } - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact import ( - defender_additional_email_configured_with_a_security_contact, - ) - - check = defender_additional_email_configured_with_a_security_contact() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"There is not another correct email configured for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert result[0].resource_id == resource_id - - def test_defender_additional_email_good_format(self): - resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, - name="default", - emails="test@test.com", - phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Contributor"] + ), + alert_minimal_severity=None, + attack_path_minimal_risk_level=None, ) } } @@ -206,139 +125,3 @@ class Test_defender_additional_email_configured_with_a_security_contact: assert result[0].subscription == AZURE_SUBSCRIPTION_ID assert result[0].resource_name == "default" assert result[0].resource_id == resource_id - - def test_defender_additional_email_good_format_multiple_subdomains(self): - resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, - name="default", - emails="test@test.mail.es; bad_mail", - phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", - ) - } - } - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact import ( - defender_additional_email_configured_with_a_security_contact, - ) - - check = defender_additional_email_configured_with_a_security_contact() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "PASS" - assert ( - result[0].status_extended - == f"There is another correct email configured for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert result[0].resource_id == resource_id - - def test_defender_default_security_contact_not_found(self): - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default": SecurityContacts( - resource_id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default", - name="default", - emails="", - phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", - ) - } - } - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact import ( - defender_additional_email_configured_with_a_security_contact, - ) - - check = defender_additional_email_configured_with_a_security_contact() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"There is not another correct email configured for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert ( - result[0].resource_id - == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default" - ) - - def test_defender_default_security_contact_not_found_empty_name(self): - resource_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default" - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, - name="", - emails="", - phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", - ) - } - } - contact = defender_client.security_contacts[AZURE_SUBSCRIPTION_ID][resource_id] - contact.name = getattr(contact, "name", "default") or "default" - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_additional_email_configured_with_a_security_contact.defender_additional_email_configured_with_a_security_contact import ( - defender_additional_email_configured_with_a_security_contact, - ) - - check = defender_additional_email_configured_with_a_security_contact() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"There is not another correct email configured for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert result[0].resource_id == resource_id diff --git a/tests/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high_test.py b/tests/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high_test.py index 229ae4259e..85355bc1f0 100644 --- a/tests/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high_test.py +++ b/tests/providers/azure/services/defender/defender_ensure_notify_alerts_severity_is_high/defender_ensure_notify_alerts_severity_is_high_test.py @@ -1,7 +1,10 @@ from unittest import mock from uuid import uuid4 -from prowler.providers.azure.services.defender.defender_service import SecurityContacts +from prowler.providers.azure.services.defender.defender_service import ( + NotificationsByRole, + SecurityContactConfiguration, +) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, set_mocked_azure_provider, @@ -10,8 +13,8 @@ from tests.providers.azure.azure_fixtures import ( class Test_defender_ensure_notify_alerts_severity_is_high: def test_defender_no_subscriptions(self): - defender_client = mock.MagicMock - defender_client.security_contacts = {} + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = {} with ( mock.patch( @@ -33,18 +36,20 @@ class Test_defender_ensure_notify_alerts_severity_is_high: def test_defender_severity_alerts_critical(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, + resource_id: SecurityContactConfiguration( + id=resource_id, name="default", - emails="", + enabled=True, + emails=[""], phone="", - alert_notifications_minimal_severity="Critical", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Contributor"] + ), + alert_minimal_severity="Critical", + attack_path_minimal_risk_level=None, ) } } @@ -77,18 +82,21 @@ class Test_defender_ensure_notify_alerts_severity_is_high: def test_defender_severity_alerts_high(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( + resource_id: SecurityContactConfiguration( resource_id=resource_id, + id=resource_id, name="default", - emails="", + enabled=True, + emails=[""], phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Contributor"] + ), + alert_minimal_severity="High", + attack_path_minimal_risk_level=None, ) } } @@ -121,18 +129,21 @@ class Test_defender_ensure_notify_alerts_severity_is_high: def test_defender_severity_alerts_low(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( + resource_id: SecurityContactConfiguration( resource_id=resource_id, + id=resource_id, name="default", - emails="", + enabled=True, + emails=[""], phone="", - alert_notifications_minimal_severity="Low", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Contributor"] + ), + alert_minimal_severity="Low", + attack_path_minimal_risk_level=None, ) } } @@ -164,18 +175,19 @@ class Test_defender_ensure_notify_alerts_severity_is_high: assert result[0].resource_id == resource_id def test_defender_default_security_contact_not_found(self): - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default": SecurityContacts( + f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default": SecurityContactConfiguration( resource_id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default", + id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default", name="default", - emails="", + enabled=True, + emails=[""], phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", + notifications_by_role=NotificationsByRole(state=True, roles=[""]), + alert_minimal_severity="", + attack_path_minimal_risk_level=None, ) } } @@ -208,50 +220,3 @@ class Test_defender_ensure_notify_alerts_severity_is_high: result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default" ) - - def test_defender_default_security_contact_not_found_empty_name(self): - resource_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default" - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, - name="", - emails="", - phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", - ) - } - } - - contact = defender_client.security_contacts[AZURE_SUBSCRIPTION_ID][resource_id] - contact.name = getattr(contact, "name", "default") or "default" - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_ensure_notify_alerts_severity_is_high.defender_ensure_notify_alerts_severity_is_high.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_ensure_notify_alerts_severity_is_high.defender_ensure_notify_alerts_severity_is_high import ( - defender_ensure_notify_alerts_severity_is_high, - ) - - check = defender_ensure_notify_alerts_severity_is_high() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"Notifications are not enabled for alerts with a minimum severity of high or lower in subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert result[0].resource_id == resource_id diff --git a/tests/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners_test.py b/tests/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners_test.py index 092a3a81a0..e4c2dc4371 100644 --- a/tests/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners_test.py +++ b/tests/providers/azure/services/defender/defender_ensure_notify_emails_to_owners/defender_ensure_notify_emails_to_owners_test.py @@ -1,7 +1,10 @@ from unittest import mock from uuid import uuid4 -from prowler.providers.azure.services.defender.defender_service import SecurityContacts +from prowler.providers.azure.services.defender.defender_service import ( + NotificationsByRole, + SecurityContactConfiguration, +) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, set_mocked_azure_provider, @@ -10,8 +13,8 @@ from tests.providers.azure.azure_fixtures import ( class Test_defender_ensure_notify_emails_to_owners: def test_defender_no_subscriptions(self): - defender_client = mock.MagicMock - defender_client.security_contacts = {} + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = {} with ( mock.patch( @@ -33,22 +36,23 @@ class Test_defender_ensure_notify_emails_to_owners: def test_defender_no_notify_emails_to_owners(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, + resource_id: SecurityContactConfiguration( + id=resource_id, name="default", - emails="", + enabled=True, + emails=[""], phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Contributor"] + ), + alert_minimal_severity="Critical", + attack_path_minimal_risk_level=None, ) } } - with ( mock.patch( "prowler.providers.common.provider.Provider.get_global_provider", @@ -67,28 +71,24 @@ class Test_defender_ensure_notify_emails_to_owners: result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"The Owner role is not notified for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" assert result[0].resource_id == resource_id def test_defender_notify_emails_to_owners_off(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, + resource_id: SecurityContactConfiguration( + id=resource_id, name="default", - emails="", + enabled=True, + emails=[""], phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Owner", "Contributor"], - notified_roles_state="Off", + notifications_by_role=NotificationsByRole( + state=False, roles=["Owner", "Contributor"] + ), + alert_minimal_severity="Critical", + attack_path_minimal_risk_level=None, ) } } @@ -121,18 +121,20 @@ class Test_defender_ensure_notify_emails_to_owners: def test_defender_notify_emails_to_owners(self): resource_id = str(uuid4()) - defender_client = mock.MagicMock - defender_client.security_contacts = { + defender_client = mock.MagicMock() + defender_client.security_contact_configurations = { AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, + resource_id: SecurityContactConfiguration( + id=resource_id, name="default", - emails="test@test.es", + enabled=True, + emails=["test@test.es"], phone="", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Owner", "Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Owner", "Contributor"] + ), + alert_minimal_severity="Critical", + attack_path_minimal_risk_level=None, ) } } @@ -162,95 +164,3 @@ class Test_defender_ensure_notify_emails_to_owners: assert result[0].subscription == AZURE_SUBSCRIPTION_ID assert result[0].resource_name == "default" assert result[0].resource_id == resource_id - - def test_defender_default_security_contact_not_found(self): - defender_client = mock.MagicMock - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default": SecurityContacts( - resource_id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default", - name="default", - emails="", - phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", - ) - } - } - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_ensure_notify_emails_to_owners.defender_ensure_notify_emails_to_owners.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_ensure_notify_emails_to_owners.defender_ensure_notify_emails_to_owners import ( - defender_ensure_notify_emails_to_owners, - ) - - check = defender_ensure_notify_emails_to_owners() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"The Owner role is not notified for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert ( - result[0].resource_id - == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default" - ) - - def test_defender_default_security_contact_not_found_empty_name(self): - defender_client = mock.MagicMock() - resource_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Security/securityContacts/default" - defender_client.security_contacts = { - AZURE_SUBSCRIPTION_ID: { - resource_id: SecurityContacts( - resource_id=resource_id, - name="", - emails="", - phone="", - alert_notifications_minimal_severity="", - alert_notifications_state="", - notified_roles=[""], - notified_roles_state="", - ) - } - } - contact = defender_client.security_contacts[AZURE_SUBSCRIPTION_ID][resource_id] - contact.name = getattr(contact, "name", "default") or "default" - - with ( - mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_azure_provider(), - ), - mock.patch( - "prowler.providers.azure.services.defender.defender_ensure_notify_emails_to_owners.defender_ensure_notify_emails_to_owners.defender_client", - new=defender_client, - ), - ): - from prowler.providers.azure.services.defender.defender_ensure_notify_emails_to_owners.defender_ensure_notify_emails_to_owners import ( - defender_ensure_notify_emails_to_owners, - ) - - check = defender_ensure_notify_emails_to_owners() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"The Owner role is not notified for subscription {AZURE_SUBSCRIPTION_ID}." - ) - assert result[0].subscription == AZURE_SUBSCRIPTION_ID - assert result[0].resource_name == "default" - assert result[0].resource_id == resource_id diff --git a/tests/providers/azure/services/defender/defender_service_test.py b/tests/providers/azure/services/defender/defender_service_test.py index ccf4260885..20156640ee 100644 --- a/tests/providers/azure/services/defender/defender_service_test.py +++ b/tests/providers/azure/services/defender/defender_service_test.py @@ -7,7 +7,7 @@ from prowler.providers.azure.services.defender.defender_service import ( Defender, IoTSecuritySolution, Pricing, - SecurityContacts, + SecurityContactConfiguration, Setting, ) from tests.providers.azure.azure_fixtures import ( @@ -55,18 +55,24 @@ def mock_defender_get_assessments(_): } -def mock_defender_get_security_contacts(_): +def mock_defender_get_security_contacts(*args, **kwargs): + from prowler.providers.azure.services.defender.defender_service import ( + NotificationsByRole, + ) + return { AZURE_SUBSCRIPTION_ID: { - "/subscriptions/resource_id": SecurityContacts( - resource_id="/subscriptions/resource_id", + "/subscriptions/resource_id": SecurityContactConfiguration( + id="/subscriptions/resource_id", name="default", - emails="user@user.com, test@test.es", + enabled=True, + emails=["user@user.com", "test@test.es"], phone="666666666", - alert_notifications_minimal_severity="High", - alert_notifications_state="On", - notified_roles=["Owner", "Contributor"], - notified_roles_state="On", + notifications_by_role=NotificationsByRole( + state=True, roles=["Owner", "Contributor"] + ), + alert_minimal_severity="High", + attack_path_minimal_risk_level=None, ) } } @@ -216,52 +222,17 @@ class Test_Defender_Service: def test_get_security_contacts(self): defender = Defender(set_mocked_azure_provider()) - assert len(defender.security_contacts) == 1 - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].resource_id - == "/subscriptions/resource_id" - ) - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].name - == "default" - ) - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].emails - == "user@user.com, test@test.es" - ) - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].phone - == "666666666" - ) - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].alert_notifications_minimal_severity - == "High" - ) - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].alert_notifications_state - == "On" - ) - assert defender.security_contacts[AZURE_SUBSCRIPTION_ID][ + assert len(defender.security_contact_configurations) == 1 + contact = defender.security_contact_configurations[AZURE_SUBSCRIPTION_ID][ "/subscriptions/resource_id" - ].notified_roles == ["Owner", "Contributor"] - assert ( - defender.security_contacts[AZURE_SUBSCRIPTION_ID][ - "/subscriptions/resource_id" - ].notified_roles_state - == "On" - ) + ] + assert contact.id == "/subscriptions/resource_id" + assert contact.name == "default" + assert contact.emails == ["user@user.com", "test@test.es"] + assert contact.phone == "666666666" + assert contact.alert_minimal_severity == "High" + assert contact.notifications_by_role.state is True + assert contact.notifications_by_role.roles == ["Owner", "Contributor"] def test_get_iot_security_solutions(self): defender = Defender(set_mocked_azure_provider())