mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
fix(azure): standardize resource_id values across Azure checks (#9994)
This commit is contained in:
committed by
GitHub
parent
cd12a9451f
commit
f7895e206b
@@ -29,6 +29,10 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- Add file descriptor limits (`ulimits`) to Docker Compose worker services to prevent `Too many open files` errors [(#10107)](https://github.com/prowler-cloud/prowler/pull/10107)
|
||||
- CIS 6.0 for the AWS provider [(#10127)](https://github.com/prowler-cloud/prowler/pull/10127)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Standardize resource_id values across Azure checks to use actual Azure resource IDs and prevent duplicate resource entries [(#9994)](https://github.com/prowler-cloud/prowler/pull/9994)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Update Azure Monitor service metadata to new format [(#9622)](https://github.com/prowler-cloud/prowler/pull/9622)
|
||||
|
||||
+4
-2
@@ -97,8 +97,10 @@ class apim_threat_detection_llm_jacking(Check):
|
||||
# 4. If no threats were found after checking all principals, create a single PASS report
|
||||
if not found_potential_llm_jacking_attackers:
|
||||
report = Check_Report_Azure(self.metadata(), resource={})
|
||||
report.resource_name = "Azure API Management"
|
||||
report.resource_id = "Azure API Management"
|
||||
report.resource_name = subscription
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{apim_client.subscriptions[subscription]}"
|
||||
)
|
||||
report.subscription = subscription
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"No potential LLM Jacking attacks detected across all monitored APIM instances in the last {threat_detection_minutes} minutes."
|
||||
|
||||
+4
-2
@@ -12,8 +12,10 @@ class appinsights_ensure_is_configured(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.status = "PASS"
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "AppInsights"
|
||||
report.resource_id = "AppInsights"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{appinsights_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status_extended = f"There is at least one AppInsight configured in subscription {subscription_name}."
|
||||
|
||||
if len(components) < 1:
|
||||
|
||||
+4
-2
@@ -14,8 +14,10 @@ class defender_ensure_iot_hub_defender_is_on(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.status = "FAIL"
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "IoT Hub Defender"
|
||||
report.resource_id = "IoT Hub Defender"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{defender_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status_extended = f"No IoT Security Solutions found in the subscription {subscription_name}."
|
||||
findings.append(report)
|
||||
else:
|
||||
|
||||
+4
-3
@@ -13,8 +13,10 @@ class defender_ensure_mcas_is_enabled(Check):
|
||||
if "MCAS" not in settings:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "MCAS"
|
||||
report.resource_id = "MCAS"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{defender_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Microsoft Defender for Cloud Apps not exists for subscription {subscription_name}."
|
||||
else:
|
||||
@@ -22,7 +24,6 @@ class defender_ensure_mcas_is_enabled(Check):
|
||||
metadata=self.metadata(), resource=settings["MCAS"]
|
||||
)
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "MCAS"
|
||||
if settings["MCAS"].enabled:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Microsoft Defender for Cloud Apps is enabled for subscription {subscription_name}."
|
||||
|
||||
+4
-3
@@ -13,8 +13,10 @@ class defender_ensure_wdatp_is_enabled(Check):
|
||||
if "WDATP" not in settings:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "WDATP"
|
||||
report.resource_id = "WDATP"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{defender_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Microsoft Defender for Endpoint integration not exists for subscription {subscription_name}."
|
||||
else:
|
||||
@@ -22,7 +24,6 @@ class defender_ensure_wdatp_is_enabled(Check):
|
||||
metadata=self.metadata(), resource=settings["WDATP"]
|
||||
)
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "WDATP"
|
||||
if settings["WDATP"].enabled:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Microsoft Defender for Endpoint integration is enabled for subscription {subscription_name}."
|
||||
|
||||
@@ -136,6 +136,7 @@ class Defender(AzureService):
|
||||
{
|
||||
setting.name: Setting(
|
||||
resource_id=setting.id,
|
||||
resource_name=setting.name or setting.id,
|
||||
resource_type=setting.type,
|
||||
kind=setting.kind,
|
||||
enabled=setting.enabled,
|
||||
@@ -311,6 +312,7 @@ class Assesment(BaseModel):
|
||||
|
||||
class Setting(BaseModel):
|
||||
resource_id: str
|
||||
resource_name: str
|
||||
resource_type: str
|
||||
kind: str
|
||||
enabled: bool
|
||||
|
||||
+7
-5
@@ -7,8 +7,10 @@ class entra_conditional_access_policy_require_mfa_for_management_api(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
|
||||
tenant_id = entra_client.tenant_ids[0]
|
||||
|
||||
for (
|
||||
tenant_name,
|
||||
tenant_domain,
|
||||
conditional_access_policies,
|
||||
) in entra_client.conditional_access_policy.items():
|
||||
for policy in conditional_access_policies.values():
|
||||
@@ -25,7 +27,7 @@ class entra_conditional_access_policy_require_mfa_for_management_api(Check):
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=policy
|
||||
)
|
||||
report.subscription = f"Tenant: {tenant_name}"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"Conditional Access Policy requires MFA for management API."
|
||||
@@ -36,9 +38,9 @@ class entra_conditional_access_policy_require_mfa_for_management_api(Check):
|
||||
metadata=self.metadata(),
|
||||
resource=conditional_access_policies,
|
||||
)
|
||||
report.subscription = f"Tenant: {tenant_name}"
|
||||
report.resource_name = "Conditional Access Policy"
|
||||
report.resource_id = "Conditional Access Policy"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = tenant_domain
|
||||
report.resource_id = tenant_id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"Conditional Access Policy does not require MFA for management API."
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_default_users_cannot_create_security_groups(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "Non-privileged users are able to create security groups via the Access Panel and the Azure administration portal."
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_ensure_default_user_cannot_create_apps(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "App creation is not disabled for non-admin users."
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"Tenants creation is not disabled for non-admin users."
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_guest_invite_only_for_admin_roles(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "Guest invitations are not restricted to users with specific administrative roles only."
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ class entra_policy_guest_users_access_restrictions(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "Guest user access is not restricted to properties and memberships of their own directory objects"
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_restricts_user_consent_for_apps(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "Entra allows users to consent apps accessing company data on their behalf"
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_user_consent_for_verified_apps(Check):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=auth_policy)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.resource_id = auth_policy.id
|
||||
report.status = "PASS"
|
||||
report.status_extended = "Entra does not allow users to consent non-verified apps accessing company data on their behalf."
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class Entra(AzureService):
|
||||
def __init__(self, provider: AzureProvider):
|
||||
super().__init__(GraphServiceClient, provider)
|
||||
|
||||
self.tenant_ids = provider.identity.tenant_ids
|
||||
|
||||
created_loop = False
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
@@ -214,6 +216,7 @@ class Entra(AzureService):
|
||||
group_settings[tenant].update(
|
||||
{
|
||||
group_setting.id: GroupSetting(
|
||||
id=group_setting.id,
|
||||
name=getattr(group_setting, "display_name", None),
|
||||
template_id=getattr(group_setting, "template_id", None),
|
||||
settings=[
|
||||
@@ -434,6 +437,7 @@ class SettingValue(BaseModel):
|
||||
|
||||
|
||||
class GroupSetting(BaseModel):
|
||||
id: str
|
||||
name: Optional[str] = None
|
||||
template_id: Optional[str] = None
|
||||
settings: List[SettingValue]
|
||||
|
||||
+18
-15
@@ -6,27 +6,30 @@ class entra_trusted_named_locations_exists(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
|
||||
for tenant, named_locations in entra_client.named_locations.items():
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=named_locations
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: {tenant}"
|
||||
report.resource_name = "Named Locations"
|
||||
report.resource_id = "Named Locations"
|
||||
report.status_extended = (
|
||||
"There is no trusted location with IP ranges defined."
|
||||
)
|
||||
tenant_id = entra_client.tenant_ids[0]
|
||||
|
||||
for tenant_domain, named_locations in entra_client.named_locations.items():
|
||||
trusted_location_found = False
|
||||
for named_location in named_locations.values():
|
||||
if named_location.ip_ranges_addresses and named_location.is_trusted:
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=named_location
|
||||
)
|
||||
report.subscription = f"Tenant: {tenant}"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Exits trusted location with trusted IP ranges, this IPs ranges are: {[ip_range for ip_range in named_location.ip_ranges_addresses if ip_range]}"
|
||||
break
|
||||
report.status_extended = f"Trusted location {named_location.name} exists with trusted IP ranges: {[ip_range for ip_range in named_location.ip_ranges_addresses if ip_range]}"
|
||||
findings.append(report)
|
||||
trusted_location_found = True
|
||||
|
||||
findings.append(report)
|
||||
if not trusted_location_found:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = tenant_domain
|
||||
report.resource_id = tenant_id
|
||||
report.status_extended = (
|
||||
"There is no trusted location with IP ranges defined."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+22
-10
@@ -6,18 +6,20 @@ class entra_users_cannot_create_microsoft_365_groups(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
|
||||
for tenant_domain, group_settings in entra_client.group_settings.items():
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=group_settings
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = "Microsoft365 Groups"
|
||||
report.resource_id = "Microsoft365 Groups"
|
||||
report.status_extended = "Users can create Microsoft 365 groups."
|
||||
tenant_id = entra_client.tenant_ids[0]
|
||||
|
||||
for tenant_domain, group_settings in entra_client.group_settings.items():
|
||||
group_unified_found = False
|
||||
for group_setting in group_settings.values():
|
||||
if group_setting.name == "Group.Unified":
|
||||
group_unified_found = True
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=group_setting
|
||||
)
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "Users can create Microsoft 365 groups."
|
||||
|
||||
for setting_value in group_setting.settings:
|
||||
if (
|
||||
getattr(setting_value, "name", "") == "EnableGroupCreation"
|
||||
@@ -29,6 +31,16 @@ class entra_users_cannot_create_microsoft_365_groups(Check):
|
||||
)
|
||||
break
|
||||
|
||||
findings.append(report)
|
||||
findings.append(report)
|
||||
break
|
||||
|
||||
if not group_unified_found:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = tenant_domain
|
||||
report.resource_id = tenant_id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "Users can create Microsoft 365 groups."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_create_policy_assignment(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for creating Policy Assignments in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_create_update_nsg(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for creating/updating Network Security Groups in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_create_update_public_ip_address_rule(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for creating/updating Public IP address rule in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_create_update_security_solution(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for creating/updating Security Solution in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_create_update_sqlserver_fr(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for creating/updating SQL Server firewall rule in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -27,8 +27,10 @@ class monitor_alert_delete_nsg(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for deleting Network Security Groups in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_delete_policy_assignment(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for deleting policy assignment in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_delete_public_ip_address_rule(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for deleting public IP address rule in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_delete_security_solution(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for deleting Security Solution in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -25,8 +25,10 @@ class monitor_alert_delete_sqlserver_fr(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is not an alert for deleting SQL Server firewall rule in subscription {subscription_name}."
|
||||
|
||||
|
||||
+4
-2
@@ -38,8 +38,10 @@ class monitor_alert_service_health_exists(Check):
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There is no activity log alert for Service Health in subscription {subscription_name}."
|
||||
|
||||
|
||||
+28
-22
@@ -10,44 +10,50 @@ class monitor_diagnostic_setting_with_appropriate_categories(Check):
|
||||
subscription_name,
|
||||
diagnostic_settings,
|
||||
) in monitor_client.diagnostics_settings.items():
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Monitor"
|
||||
report.resource_id = "Monitor"
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no diagnostic settings capturing appropiate categories in subscription {subscription_name}."
|
||||
administrative_enabled = False
|
||||
security_enabled = False
|
||||
service_health_enabled = False
|
||||
alert_enabled = False
|
||||
compliant_setting = None
|
||||
|
||||
for diagnostic_setting in diagnostic_settings:
|
||||
administrative_enabled = False
|
||||
security_enabled = False
|
||||
alert_enabled = False
|
||||
policy_enabled = False
|
||||
|
||||
for log in diagnostic_setting.logs:
|
||||
if log.category == "Administrative" and log.enabled:
|
||||
administrative_enabled = True
|
||||
if log.category == "Security" and log.enabled:
|
||||
security_enabled = True
|
||||
if log.category == "Alert" and log.enabled:
|
||||
service_health_enabled = True
|
||||
if log.category == "Policy" and log.enabled:
|
||||
alert_enabled = True
|
||||
if log.category == "Policy" and log.enabled:
|
||||
policy_enabled = True
|
||||
|
||||
if (
|
||||
administrative_enabled
|
||||
and security_enabled
|
||||
and service_health_enabled
|
||||
and alert_enabled
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"There is at least one diagnostic setting capturing appropiate categories in subscription {subscription_name}."
|
||||
break
|
||||
if (
|
||||
administrative_enabled
|
||||
and security_enabled
|
||||
and service_health_enabled
|
||||
and alert_enabled
|
||||
and policy_enabled
|
||||
):
|
||||
compliant_setting = diagnostic_setting
|
||||
break
|
||||
|
||||
if compliant_setting:
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=compliant_setting
|
||||
)
|
||||
report.subscription = subscription_name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Diagnostic setting {compliant_setting.name} captures appropriate categories in subscription {subscription_name}."
|
||||
else:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"No diagnostic setting captures all appropriate categories (Administrative, Security, Alert, Policy) in subscription {subscription_name}."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+17
-9
@@ -10,18 +10,26 @@ class monitor_diagnostic_settings_exists(Check):
|
||||
subscription_name,
|
||||
diagnostic_settings,
|
||||
) in monitor_client.diagnostics_settings.items():
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = "Diagnostic Settings"
|
||||
report.resource_id = "diagnostic_settings"
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"No diagnostic settings found in subscription {subscription_name}."
|
||||
)
|
||||
if diagnostic_settings:
|
||||
# At least one diagnostic setting exists - report on the first one
|
||||
diagnostic_setting = diagnostic_settings[0]
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=diagnostic_setting
|
||||
)
|
||||
report.subscription = subscription_name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Diagnostic setting {diagnostic_setting.name} found in subscription {subscription_name}."
|
||||
else:
|
||||
# No diagnostic settings - report on subscription
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = subscription_name
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{monitor_client.subscriptions[subscription_name]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Diagnostic settings found in subscription {subscription_name}."
|
||||
f"No diagnostic settings found in subscription {subscription_name}."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
+17
-15
@@ -7,23 +7,25 @@ class network_bastion_host_exists(Check):
|
||||
findings = []
|
||||
for subscription, bastion_hosts in network_client.bastion_hosts.items():
|
||||
if not bastion_hosts:
|
||||
status = "FAIL"
|
||||
status_extended = (
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription
|
||||
report.resource_name = subscription
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{network_client.subscriptions[subscription]}"
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Bastion Host from subscription {subscription} does not exist"
|
||||
)
|
||||
findings.append(report)
|
||||
else:
|
||||
bastion_names = ", ".join(
|
||||
[bastion_host.name for bastion_host in bastion_hosts]
|
||||
)
|
||||
status = "PASS"
|
||||
status_extended = f"Bastion Host from subscription {subscription} available are: {bastion_names}"
|
||||
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription
|
||||
report.resource_name = "Bastion Host"
|
||||
report.resource_id = "Bastion Host"
|
||||
report.status = status
|
||||
report.status_extended = status_extended
|
||||
findings.append(report)
|
||||
for bastion_host in bastion_hosts:
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=bastion_host
|
||||
)
|
||||
report.subscription = subscription
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Bastion Host {bastion_host.name} exists in subscription {subscription}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+18
-10
@@ -6,23 +6,31 @@ class network_watcher_enabled(Check):
|
||||
def execute(self) -> list[Check_Report_Azure]:
|
||||
findings = []
|
||||
for subscription, network_watchers in network_client.network_watchers.items():
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription
|
||||
report.resource_name = "Network Watcher"
|
||||
report.location = "global"
|
||||
report.resource_id = f"/subscriptions/{network_client.subscriptions[subscription]}/resourceGroups/NetworkWatcherRG/providers/Microsoft.Network/networkWatchers/NetworkWatcher_*"
|
||||
|
||||
missing_locations = set(network_client.locations[subscription]) - set(
|
||||
network_watcher.location for network_watcher in network_watchers
|
||||
)
|
||||
|
||||
if missing_locations:
|
||||
# Report against the subscription when network watchers are missing
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource={})
|
||||
report.subscription = subscription
|
||||
report.resource_name = subscription
|
||||
report.resource_id = (
|
||||
f"/subscriptions/{network_client.subscriptions[subscription]}"
|
||||
)
|
||||
report.location = "global"
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Network Watcher is not enabled for the following locations in subscription '{subscription}': {', '.join(missing_locations)}."
|
||||
findings.append(report)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Network Watcher is enabled for all locations in subscription '{subscription}'."
|
||||
|
||||
findings.append(report)
|
||||
# Report each network watcher that exists
|
||||
for network_watcher in network_watchers:
|
||||
report = Check_Report_Azure(
|
||||
metadata=self.metadata(), resource=network_watcher
|
||||
)
|
||||
report.subscription = subscription
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Network Watcher {network_watcher.name} is enabled in location {network_watcher.location} in subscription '{subscription}'."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+7
@@ -184,6 +184,7 @@ class Test_apim_threat_detection_llm_jacking:
|
||||
)
|
||||
]
|
||||
}
|
||||
apim_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
apim_client.audit_config = {
|
||||
"apim_threat_detection_llm_jacking_threshold": 0.9,
|
||||
"apim_threat_detection_llm_jacking_minutes": 1440,
|
||||
@@ -301,6 +302,7 @@ class Test_apim_threat_detection_llm_jacking:
|
||||
)
|
||||
]
|
||||
}
|
||||
apim_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
apim_client.audit_config = {
|
||||
"apim_threat_detection_llm_jacking_threshold": 0.9,
|
||||
"apim_threat_detection_llm_jacking_minutes": 1440,
|
||||
@@ -365,6 +367,7 @@ class Test_apim_threat_detection_llm_jacking:
|
||||
)
|
||||
]
|
||||
}
|
||||
apim_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
apim_client.audit_config = {
|
||||
"apim_threat_detection_llm_jacking_threshold": 0.9,
|
||||
"apim_threat_detection_llm_jacking_minutes": 1440,
|
||||
@@ -436,6 +439,10 @@ class Test_apim_threat_detection_llm_jacking:
|
||||
)
|
||||
],
|
||||
}
|
||||
apim_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID,
|
||||
"another-subscription": "another-subscription-id",
|
||||
}
|
||||
apim_client.audit_config = {
|
||||
"apim_threat_detection_llm_jacking_threshold": 0.9,
|
||||
"apim_threat_detection_llm_jacking_minutes": 1440,
|
||||
|
||||
+11
-6
@@ -33,6 +33,9 @@ class Test_appinsights_ensure_is_configured:
|
||||
def test_no_appinsights(self):
|
||||
appinsights_client = mock.MagicMock
|
||||
appinsights_client.components = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
appinsights_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -53,9 +56,8 @@ class Test_appinsights_ensure_is_configured:
|
||||
assert len(result) == 1
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_id == "AppInsights"
|
||||
assert result[0].resource_name == "AppInsights"
|
||||
assert result[0].location == "global"
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There are no AppInsight configured in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
@@ -66,13 +68,16 @@ class Test_appinsights_ensure_is_configured:
|
||||
appinsights_client.components = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"app_id-1": Component(
|
||||
resource_id="/subscriptions/resource_id",
|
||||
resource_id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/test-rg/providers/microsoft.insights/components/AppInsightsTest",
|
||||
resource_name="AppInsightsTest",
|
||||
location="westeurope",
|
||||
instrumentation_key="",
|
||||
)
|
||||
}
|
||||
}
|
||||
appinsights_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -93,8 +98,8 @@ class Test_appinsights_ensure_is_configured:
|
||||
assert len(result) == 1
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == "AppInsights"
|
||||
assert result[0].resource_name == "AppInsights"
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "global"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
|
||||
+3
-2
@@ -36,6 +36,7 @@ class Test_defender_ensure_iot_hub_defender_is_on:
|
||||
def test_defender_no_iot_hub_solutions(self):
|
||||
defender_client = mock.MagicMock
|
||||
defender_client.iot_security_solutions = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
defender_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -59,8 +60,8 @@ class Test_defender_ensure_iot_hub_defender_is_on:
|
||||
result[0].status_extended
|
||||
== f"No IoT Security Solutions found in the subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
assert result[0].resource_name == "IoT Hub Defender"
|
||||
assert result[0].resource_id == "IoT Hub Defender"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
|
||||
def test_defender_iot_hub_solution_disabled(self):
|
||||
resource_id = str(uuid4())
|
||||
|
||||
+5
-2
@@ -38,6 +38,7 @@ class Test_defender_ensure_mcas_is_enabled:
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"MCAS": Setting(
|
||||
resource_id=resource_id,
|
||||
resource_name="MCAS",
|
||||
resource_type="Microsoft.Security/locations/settings",
|
||||
kind="DataExportSettings",
|
||||
enabled=False,
|
||||
@@ -78,6 +79,7 @@ class Test_defender_ensure_mcas_is_enabled:
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"MCAS": Setting(
|
||||
resource_id=resource_id,
|
||||
resource_name="MCAS",
|
||||
resource_type="Microsoft.Security/locations/settings",
|
||||
kind="DataExportSettings",
|
||||
enabled=True,
|
||||
@@ -114,6 +116,7 @@ class Test_defender_ensure_mcas_is_enabled:
|
||||
def test_defender_mcas_no_settings(self):
|
||||
defender_client = mock.MagicMock
|
||||
defender_client.settings = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
defender_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -138,5 +141,5 @@ class Test_defender_ensure_mcas_is_enabled:
|
||||
== f"Microsoft Defender for Cloud Apps not exists for subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "MCAS"
|
||||
assert result[0].resource_id == "MCAS"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
|
||||
+5
-2
@@ -38,6 +38,7 @@ class Test_defender_ensure_wdatp_is_enabled:
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"WDATP": Setting(
|
||||
resource_id=resource_id,
|
||||
resource_name="WDATP",
|
||||
resource_type="Microsoft.Security/locations/settings",
|
||||
kind="DataExportSettings",
|
||||
enabled=False,
|
||||
@@ -78,6 +79,7 @@ class Test_defender_ensure_wdatp_is_enabled:
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"WDATP": Setting(
|
||||
resource_id=resource_id,
|
||||
resource_name="WDATP",
|
||||
resource_type="Microsoft.Security/locations/settings",
|
||||
kind="DataExportSettings",
|
||||
enabled=True,
|
||||
@@ -114,6 +116,7 @@ class Test_defender_ensure_wdatp_is_enabled:
|
||||
def test_defender_wdatp_no_settings(self):
|
||||
defender_client = mock.MagicMock
|
||||
defender_client.settings = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
defender_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -138,5 +141,5 @@ class Test_defender_ensure_wdatp_is_enabled:
|
||||
== f"Microsoft Defender for Endpoint integration not exists for subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "WDATP"
|
||||
assert result[0].resource_id == "WDATP"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
|
||||
@@ -84,6 +84,7 @@ def mock_defender_get_settings(_):
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"MCAS": Setting(
|
||||
resource_id="/subscriptions/resource_id",
|
||||
resource_name="MCAS",
|
||||
resource_type="Microsoft.Security/locations/settings",
|
||||
kind="DataExportSettings",
|
||||
enabled=True,
|
||||
|
||||
+27
-11
@@ -1,7 +1,11 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_provider
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
DOMAIN,
|
||||
TENANT_IDS,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
@@ -23,6 +27,7 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policy = {}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
@@ -45,15 +50,17 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
entra_conditional_access_policy_require_mfa_for_management_api,
|
||||
)
|
||||
|
||||
# No policies configured
|
||||
entra_client.conditional_access_policy = {DOMAIN: {}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Conditional Access Policy"
|
||||
assert result[0].resource_id == "Conditional Access Policy"
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy does not require MFA for management API."
|
||||
@@ -90,14 +97,16 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Conditional Access Policy"
|
||||
assert result[0].resource_id == "Conditional Access Policy"
|
||||
# When policy exists but doesn't meet requirements, resource defaults to tenant
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy does not require MFA for management API."
|
||||
@@ -134,6 +143,7 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
@@ -178,14 +188,16 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Conditional Access Policy"
|
||||
assert result[0].resource_id == "Conditional Access Policy"
|
||||
# When policy is disabled, resource defaults to tenant
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy does not require MFA for management API."
|
||||
@@ -222,14 +234,16 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Conditional Access Policy"
|
||||
assert result[0].resource_id == "Conditional Access Policy"
|
||||
# When policy doesn't target management API, resource defaults to tenant
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy does not require MFA for management API."
|
||||
@@ -266,14 +280,16 @@ class Test_entra_conditional_access_policy_require_mfa_for_management_api:
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_conditional_access_policy_require_mfa_for_management_api()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Conditional Access Policy"
|
||||
assert result[0].resource_id == "Conditional Access Policy"
|
||||
# When policy doesn't include all users, resource defaults to tenant
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy does not require MFA for management API."
|
||||
|
||||
+16
-2
@@ -29,7 +29,7 @@ class Test_entra_policy_default_users_cannot_create_security_groups:
|
||||
|
||||
def test_entra_tenant_empty(self):
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.authorization_policy = {DOMAIN: {}}
|
||||
id = str(uuid4())
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -44,6 +44,20 @@ class Test_entra_policy_default_users_cannot_create_security_groups:
|
||||
from prowler.providers.azure.services.entra.entra_policy_default_users_cannot_create_security_groups.entra_policy_default_users_cannot_create_security_groups import (
|
||||
entra_policy_default_users_cannot_create_security_groups,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
)
|
||||
|
||||
# Policy with no default user role permissions
|
||||
entra_client.authorization_policy = {
|
||||
DOMAIN: AuthorizationPolicy(
|
||||
id=id,
|
||||
name="Authorization Policy",
|
||||
description="Default policy",
|
||||
guest_invite_settings="everyone",
|
||||
guest_user_role_id=uuid4(),
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_policy_default_users_cannot_create_security_groups()
|
||||
result = check.execute()
|
||||
@@ -51,7 +65,7 @@ class Test_entra_policy_default_users_cannot_create_security_groups:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Non-privileged users are able to create security groups via the Access Panel and the Azure administration portal."
|
||||
|
||||
+15
-2
@@ -30,6 +30,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps:
|
||||
|
||||
def test_entra_tenant_empty(self):
|
||||
entra_client = mock.MagicMock
|
||||
id = str(uuid4())
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -44,8 +45,20 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps:
|
||||
from prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_apps.entra_policy_ensure_default_user_cannot_create_apps import (
|
||||
entra_policy_ensure_default_user_cannot_create_apps,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
)
|
||||
|
||||
entra_client.authorization_policy = {DOMAIN: {}}
|
||||
# Policy with no default user role permissions
|
||||
entra_client.authorization_policy = {
|
||||
DOMAIN: AuthorizationPolicy(
|
||||
id=id,
|
||||
name="Authorization Policy",
|
||||
description="Default policy",
|
||||
guest_invite_settings="none",
|
||||
guest_user_role_id=uuid4(),
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_policy_ensure_default_user_cannot_create_apps()
|
||||
result = check.execute()
|
||||
@@ -53,7 +66,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "App creation is not disabled for non-admin users."
|
||||
|
||||
+16
-2
@@ -29,7 +29,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants:
|
||||
|
||||
def test_entra_empty_tenant(self):
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.authorization_policy = {DOMAIN: {}}
|
||||
id = str(uuid4())
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -44,6 +44,20 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants:
|
||||
from prowler.providers.azure.services.entra.entra_policy_ensure_default_user_cannot_create_tenants.entra_policy_ensure_default_user_cannot_create_tenants import (
|
||||
entra_policy_ensure_default_user_cannot_create_tenants,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
)
|
||||
|
||||
# Policy with no default user role permissions
|
||||
entra_client.authorization_policy = {
|
||||
DOMAIN: AuthorizationPolicy(
|
||||
id=id,
|
||||
name="Authorization Policy",
|
||||
description="Default policy",
|
||||
guest_invite_settings="everyone",
|
||||
guest_user_role_id=uuid4(),
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_policy_ensure_default_user_cannot_create_tenants()
|
||||
result = check.execute()
|
||||
@@ -51,7 +65,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Tenants creation is not disabled for non-admin users."
|
||||
|
||||
+17
-2
@@ -30,6 +30,7 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
|
||||
def test_entra_empty_tenant(self):
|
||||
entra_client = mock.MagicMock
|
||||
id = str(uuid4())
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -44,8 +45,22 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
from prowler.providers.azure.services.entra.entra_policy_guest_invite_only_for_admin_roles.entra_policy_guest_invite_only_for_admin_roles import (
|
||||
entra_policy_guest_invite_only_for_admin_roles,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
DefaultUserRolePermissions,
|
||||
)
|
||||
|
||||
entra_client.authorization_policy = {DOMAIN: {}}
|
||||
# Policy with default settings (everyone can invite guests)
|
||||
entra_client.authorization_policy = {
|
||||
DOMAIN: AuthorizationPolicy(
|
||||
id=id,
|
||||
name="Authorization Policy",
|
||||
description="Default policy",
|
||||
default_user_role_permissions=DefaultUserRolePermissions(),
|
||||
guest_invite_settings="everyone",
|
||||
guest_user_role_id=uuid4(),
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_policy_guest_invite_only_for_admin_roles()
|
||||
result = check.execute()
|
||||
@@ -53,7 +68,7 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Guest invitations are not restricted to users with specific administrative roles only."
|
||||
|
||||
+15
-2
@@ -30,6 +30,7 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
|
||||
def test_entra_tenant_empty(self):
|
||||
entra_client = mock.MagicMock
|
||||
id = str(uuid4())
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -44,8 +45,20 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
from prowler.providers.azure.services.entra.entra_policy_guest_users_access_restrictions.entra_policy_guest_users_access_restrictions import (
|
||||
entra_policy_guest_users_access_restrictions,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
)
|
||||
|
||||
entra_client.authorization_policy = {DOMAIN: {}}
|
||||
# Policy with guest user role set to same as member (not restricted)
|
||||
entra_client.authorization_policy = {
|
||||
DOMAIN: AuthorizationPolicy(
|
||||
id=id,
|
||||
name="Authorization Policy",
|
||||
description="",
|
||||
guest_invite_settings="none",
|
||||
guest_user_role_id=UUID("a0b1b346-4d3e-4e8b-98f8-753987be4970"),
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_policy_guest_users_access_restrictions()
|
||||
result = check.execute()
|
||||
@@ -53,7 +66,7 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Guest user access is not restricted to properties and memberships of their own directory objects"
|
||||
|
||||
+15
-2
@@ -30,6 +30,7 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
|
||||
def test_entra_tenant_empty(self):
|
||||
entra_client = mock.MagicMock
|
||||
id = str(uuid4())
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -44,8 +45,20 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
from prowler.providers.azure.services.entra.entra_policy_restricts_user_consent_for_apps.entra_policy_restricts_user_consent_for_apps import (
|
||||
entra_policy_restricts_user_consent_for_apps,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
)
|
||||
|
||||
entra_client.authorization_policy = {DOMAIN: {}}
|
||||
# Policy with no default user role permissions
|
||||
entra_client.authorization_policy = {
|
||||
DOMAIN: AuthorizationPolicy(
|
||||
id=id,
|
||||
name="Authorization Policy",
|
||||
description="Default policy",
|
||||
guest_invite_settings="none",
|
||||
guest_user_role_id=uuid4(),
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_policy_restricts_user_consent_for_apps()
|
||||
result = check.execute()
|
||||
@@ -53,7 +66,7 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Entra allows users to consent apps accessing company data on their behalf"
|
||||
|
||||
@@ -41,6 +41,7 @@ async def mock_entra_get_group_settings(_):
|
||||
return {
|
||||
DOMAIN: {
|
||||
"id-1": GroupSetting(
|
||||
id="id-1",
|
||||
name="Test",
|
||||
template_id="id-group-setting",
|
||||
settings=[],
|
||||
|
||||
+20
-8
@@ -1,6 +1,10 @@
|
||||
from unittest import mock
|
||||
|
||||
from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_provider
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
DOMAIN,
|
||||
TENANT_IDS,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_entra_trusted_named_locations_exists:
|
||||
@@ -22,6 +26,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
)
|
||||
|
||||
entra_client.named_locations = {}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_trusted_named_locations_exists()
|
||||
result = check.execute()
|
||||
@@ -44,7 +49,9 @@ class Test_entra_trusted_named_locations_exists:
|
||||
entra_trusted_named_locations_exists,
|
||||
)
|
||||
|
||||
# No named locations configured
|
||||
entra_client.named_locations = {DOMAIN: {}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_trusted_named_locations_exists()
|
||||
result = check.execute()
|
||||
@@ -55,8 +62,8 @@ class Test_entra_trusted_named_locations_exists:
|
||||
== "There is no trusted location with IP ranges defined."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Named Locations"
|
||||
assert result[0].resource_id == "Named Locations"
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
|
||||
def test_entra_named_location_with_ip_ranges(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -88,6 +95,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
)
|
||||
}
|
||||
}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_trusted_named_locations_exists()
|
||||
result = check.execute()
|
||||
@@ -95,7 +103,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Exits trusted location with trusted IP ranges, this IPs ranges are: ['192.168.0.1/24']"
|
||||
== "Trusted location Test Location exists with trusted IP ranges: ['192.168.0.1/24']"
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Test Location"
|
||||
@@ -131,6 +139,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
)
|
||||
}
|
||||
}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_trusted_named_locations_exists()
|
||||
result = check.execute()
|
||||
@@ -141,8 +150,9 @@ class Test_entra_trusted_named_locations_exists:
|
||||
== "There is no trusted location with IP ranges defined."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Named Locations"
|
||||
assert result[0].resource_id == "Named Locations"
|
||||
# When no trusted location found, resource defaults to tenant
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
|
||||
def test_entra_new_named_location_with_ip_ranges_not_trusted(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -174,6 +184,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
)
|
||||
}
|
||||
}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_trusted_named_locations_exists()
|
||||
result = check.execute()
|
||||
@@ -184,5 +195,6 @@ class Test_entra_trusted_named_locations_exists:
|
||||
== "There is no trusted location with IP ranges defined."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Named Locations"
|
||||
assert result[0].resource_id == "Named Locations"
|
||||
# When location exists but is not trusted, resource defaults to tenant
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
|
||||
+22
-9
@@ -1,7 +1,11 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_provider
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
DOMAIN,
|
||||
TENANT_IDS,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
@@ -23,6 +27,7 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
)
|
||||
|
||||
entra_client.group_settings = {}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_users_cannot_create_microsoft_365_groups()
|
||||
result = check.execute()
|
||||
@@ -45,7 +50,9 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
entra_users_cannot_create_microsoft_365_groups,
|
||||
)
|
||||
|
||||
# Empty group settings - no Group.Unified found
|
||||
entra_client.group_settings = {DOMAIN: {}}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_users_cannot_create_microsoft_365_groups()
|
||||
result = check.execute()
|
||||
@@ -53,8 +60,8 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Users can create Microsoft 365 groups."
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
assert result[0].resource_name == DOMAIN
|
||||
assert result[0].resource_id == TENANT_IDS[0]
|
||||
|
||||
def test_entra_users_cannot_create_microsoft_365_groups(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -85,12 +92,14 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
entra_client.group_settings = {
|
||||
DOMAIN: {
|
||||
id: GroupSetting(
|
||||
id=id,
|
||||
name="Group.Unified",
|
||||
template_id=template_id,
|
||||
settings=[setting],
|
||||
)
|
||||
}
|
||||
}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_users_cannot_create_microsoft_365_groups()
|
||||
result = check.execute()
|
||||
@@ -100,8 +109,8 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
result[0].status_extended == "Users cannot create Microsoft 365 groups."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
assert result[0].resource_name == "Group.Unified"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
def test_entra_users_can_create_microsoft_365_groups(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -132,12 +141,14 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
entra_client.group_settings = {
|
||||
DOMAIN: {
|
||||
id: GroupSetting(
|
||||
id=id,
|
||||
name="Group.Unified",
|
||||
template_id=template_id,
|
||||
settings=[setting],
|
||||
)
|
||||
}
|
||||
}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_users_cannot_create_microsoft_365_groups()
|
||||
result = check.execute()
|
||||
@@ -145,8 +156,8 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Users can create Microsoft 365 groups."
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
assert result[0].resource_name == "Group.Unified"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
def test_entra_users_can_create_microsoft_365_groups_no_setting(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -174,12 +185,14 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
entra_client.group_settings = {
|
||||
DOMAIN: {
|
||||
id: GroupSetting(
|
||||
id=id,
|
||||
name="Group.Unified",
|
||||
template_id=template_id,
|
||||
settings=[],
|
||||
)
|
||||
}
|
||||
}
|
||||
entra_client.tenant_ids = TENANT_IDS
|
||||
|
||||
check = entra_users_cannot_create_microsoft_365_groups()
|
||||
result = check.execute()
|
||||
@@ -187,5 +200,5 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Users can create Microsoft 365 groups."
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
assert result[0].resource_name == "Group.Unified"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ class Test_monitor_alert_create_policy_assignment:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -53,8 +54,8 @@ class Test_monitor_alert_create_policy_assignment:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for creating Policy Assignments in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_create_update_nsg:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_create_update_nsg:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for creating/updating Network Security Groups in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for creating/updating Public IP address rule in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for creating/updating Security Solution in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_create_update_sqlserver_fr:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_create_update_sqlserver_fr:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for creating/updating SQL Server firewall rule in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_delete_nsg:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_delete_nsg:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for deleting Network Security Groups in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ class Test_monitor_alert_delete_policy_assignment:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -53,8 +54,8 @@ class Test_monitor_alert_delete_policy_assignment:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for deleting policy assignment in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for deleting public IP address rule in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_create_update_security_solution:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for deleting Security Solution in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ class Test_monitor_alert_delete_sqlserver_fr:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -52,8 +53,8 @@ class Test_monitor_alert_delete_sqlserver_fr:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is not an alert for deleting SQL Server firewall rule in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+8
-4
@@ -31,6 +31,7 @@ class Test_monitor_alert_service_health_exists:
|
||||
def test_no_alert_rules(self):
|
||||
monitor_client = mock.MagicMock()
|
||||
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -50,8 +51,8 @@ class Test_monitor_alert_service_health_exists:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is no activity log alert for Service Health in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
@@ -151,13 +152,16 @@ class Test_monitor_alert_service_health_exists:
|
||||
),
|
||||
]
|
||||
}
|
||||
monitor_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID
|
||||
}
|
||||
check = monitor_alert_service_health_exists()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is no activity log alert for Service Health in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
|
||||
+9
-7
@@ -23,7 +23,6 @@ class Test_monitor_diagnostic_setting_with_appropriate_categories:
|
||||
new=monitor_client,
|
||||
),
|
||||
):
|
||||
|
||||
from prowler.providers.azure.services.monitor.monitor_diagnostic_setting_with_appropriate_categories.monitor_diagnostic_setting_with_appropriate_categories import (
|
||||
monitor_diagnostic_setting_with_appropriate_categories,
|
||||
)
|
||||
@@ -35,6 +34,7 @@ class Test_monitor_diagnostic_setting_with_appropriate_categories:
|
||||
def test_no_diagnostic_settings(self):
|
||||
monitor_client = mock.MagicMock
|
||||
monitor_client.diagnostics_settings = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -54,11 +54,11 @@ class Test_monitor_diagnostic_setting_with_appropriate_categories:
|
||||
assert len(result) == 1
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There are no diagnostic settings capturing appropiate categories in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
== f"No diagnostic setting captures all appropriate categories (Administrative, Security, Alert, Policy) in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
|
||||
def test_diagnostic_settings_configured(self):
|
||||
@@ -119,12 +119,14 @@ class Test_monitor_diagnostic_setting_with_appropriate_categories:
|
||||
}
|
||||
check = monitor_diagnostic_setting_with_appropriate_categories()
|
||||
result = check.execute()
|
||||
# Now returns only one finding per subscription (first compliant setting found)
|
||||
assert len(result) == 1
|
||||
# First diagnostic setting has all required categories enabled
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == "Monitor"
|
||||
assert result[0].resource_name == "Monitor"
|
||||
assert result[0].resource_id == "id"
|
||||
assert result[0].resource_name == "name"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"There is at least one diagnostic setting capturing appropiate categories in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
== f"Diagnostic setting name captures appropriate categories in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
|
||||
+7
-2
@@ -7,7 +7,6 @@ from tests.providers.azure.azure_fixtures import (
|
||||
|
||||
|
||||
class Test_monitor_diagnostic_settings_exists:
|
||||
|
||||
def test_monitor_diagnostic_settings_exists_no_subscriptions(
|
||||
self,
|
||||
):
|
||||
@@ -35,6 +34,7 @@ class Test_monitor_diagnostic_settings_exists:
|
||||
def test_no_diagnostic_settings(self):
|
||||
monitor_client = mock.MagicMock
|
||||
monitor_client.diagnostics_settings = {AZURE_SUBSCRIPTION_ID: []}
|
||||
monitor_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -54,6 +54,8 @@ class Test_monitor_diagnostic_settings_exists:
|
||||
assert len(result) == 1
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"No diagnostic settings found in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
@@ -186,10 +188,13 @@ class Test_monitor_diagnostic_settings_exists:
|
||||
}
|
||||
check = monitor_diagnostic_settings_exists()
|
||||
result = check.execute()
|
||||
# Now returns only one finding per subscription (first diagnostic setting found)
|
||||
assert len(result) == 1
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_name == "name"
|
||||
assert result[0].resource_id == "id"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Diagnostic settings found in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
== f"Diagnostic setting name found in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
|
||||
+6
-5
@@ -12,6 +12,7 @@ class Test_network_bastion_host_exists:
|
||||
def test_no_bastion_hosts(self):
|
||||
network_client = mock.MagicMock
|
||||
network_client.bastion_hosts = {AZURE_SUBSCRIPTION_ID: []}
|
||||
network_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -40,8 +41,8 @@ class Test_network_bastion_host_exists:
|
||||
== f"Bastion Host from subscription {AZURE_SUBSCRIPTION_ID} does not exist"
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Bastion Host"
|
||||
assert result[0].resource_id == "Bastion Host"
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
|
||||
def test_network_bastion_host_exists(self):
|
||||
network_client = mock.MagicMock
|
||||
@@ -82,8 +83,8 @@ class Test_network_bastion_host_exists:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Bastion Host from subscription {AZURE_SUBSCRIPTION_ID} available are: {bastion_host_name}"
|
||||
== f"Bastion Host {bastion_host_name} exists in subscription {AZURE_SUBSCRIPTION_ID}."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == "Bastion Host"
|
||||
assert result[0].resource_id == "Bastion Host"
|
||||
assert result[0].resource_name == bastion_host_name
|
||||
assert result[0].resource_id == bastion_host_id
|
||||
|
||||
+3
-4
@@ -84,8 +84,8 @@ class Test_network_watcher_enabled:
|
||||
== f"Network Watcher is not enabled for the following locations in subscription '{AZURE_SUBSCRIPTION_NAME}': location."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_NAME
|
||||
assert result[0].resource_name == network_watcher_name
|
||||
assert result[0].resource_id == network_watcher_id
|
||||
assert result[0].resource_name == AZURE_SUBSCRIPTION_NAME
|
||||
assert result[0].resource_id == f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_network_valid_network_watchers(self):
|
||||
@@ -131,9 +131,8 @@ class Test_network_watcher_enabled:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Network Watcher is enabled for all locations in subscription '{AZURE_SUBSCRIPTION_NAME}'."
|
||||
== f"Network Watcher {network_watcher_name} is enabled in location location in subscription '{AZURE_SUBSCRIPTION_NAME}'."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_NAME
|
||||
assert result[0].resource_name == network_watcher_name
|
||||
assert result[0].resource_id == network_watcher_id
|
||||
assert result[0].location == "global"
|
||||
|
||||
Reference in New Issue
Block a user