chore(azure/defender): get security contacts from API REST (#8241)

This commit is contained in:
Rubén De la Torre Vico
2025-07-15 16:37:43 +02:00
committed by GitHub
parent 4b62fdcf53
commit a09be4c0ba
9 changed files with 274 additions and 597 deletions
+1
View File
@@ -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)
@@ -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}."
@@ -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)
@@ -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}."
@@ -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):
@@ -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
@@ -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
@@ -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
@@ -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())