From 7dd2038cc6378442c655f1fcdbfee793e7b9a39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 24 Jun 2025 11:37:05 +0200 Subject: [PATCH] fix(azure): consolidate file share properties to the storage account level (#8087) (cherry picked from commit e0465f2aa2f738692b16abe4a70527248cc82d6f) # Conflicts: # prowler/CHANGELOG.md # prowler/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled.py # prowler/providers/azure/services/storage/storage_service.py # tests/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled_test.py # tests/providers/azure/services/storage/storage_service_test.py --- prowler/CHANGELOG.md | 18 ++ ...sure_file_shares_soft_delete_is_enabled.py | 30 +++ .../azure/services/storage/storage_service.py | 68 ++++++ ...file_shares_soft_delete_is_enabled_test.py | 194 ++++++++++++++++++ .../services/storage/storage_service_test.py | 37 ++++ 5 files changed, 347 insertions(+) create mode 100644 prowler/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled.py create mode 100644 tests/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f211645574..5882cdf485 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -27,7 +27,25 @@ All notable changes to the **Prowler SDK** are documented in this file. - `storage_ensure_file_shares_soft_delete_is_enabled` check for Azure provider [(#7966)](https://github.com/prowler-cloud/prowler/pull/7966) - Make `validate_mutelist` method static inside `Mutelist` class [(#7811)](https://github.com/prowler-cloud/prowler/pull/7811) - Avoid bypassing IAM check using wildcards [(#7708)](https://github.com/prowler-cloud/prowler/pull/7708) +<<<<<<< HEAD - Add new method to authenticate in AppInsights in check `app_function_application_insights_enabled` [(#7763)](https://github.com/prowler-cloud/prowler/pull/7763) +======= +- `storage_blob_versioning_is_enabled` new check for Azure provider [(#7927)](https://github.com/prowler-cloud/prowler/pull/7927) +- New method to authenticate in AppInsights in check `app_function_application_insights_enabled` [(#7763)](https://github.com/prowler-cloud/prowler/pull/7763) +- ISO 27001 2022 for M365 provider. [(#7985)](https://github.com/prowler-cloud/prowler/pull/7985) +- `codebuild_project_uses_allowed_github_organizations` check for AWS provider [(#7595)](https://github.com/prowler-cloud/prowler/pull/7595) +- IaC provider [(#7852)](https://github.com/prowler-cloud/prowler/pull/7852) +- Azure Databricks service integration for Azure provider, including the `databricks_workspace_vnet_injection_enabled` check [(#8008)](https://github.com/prowler-cloud/prowler/pull/8008) +- Azure Databricks check `databricks_workspace_cmk_encryption_enabled` to ensure workspaces use customer-managed keys (CMK) for encryption at rest [(#8017)](https://github.com/prowler-cloud/prowler/pull/8017) +- Add `storage_account_default_to_entra_authorization_enabled` check for Azure provider. [(#7981)](https://github.com/prowler-cloud/prowler/pull/7981) +- Replace `Domain.Read.All` with `Directory.Read.All` in Azure and M365 docs [(#8075)](https://github.com/prowler-cloud/prowler/pull/8075) + +### Fixed +- Consolidate Azure Storage file service properties to the account level, improving the accuracy of the `storage_ensure_file_shares_soft_delete_is_enabled` check [(#8087)](https://github.com/prowler-cloud/prowler/pull/8087) + +### Removed +- OCSF version number references to point always to the latest [(#8064)](https://github.com/prowler-cloud/prowler/pull/8064) +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) --- ## [v5.7.5] (Prowler UNRELEASED) diff --git a/prowler/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled.py b/prowler/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled.py new file mode 100644 index 0000000000..cf92ee25f3 --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.storage.storage_client import storage_client + + +class storage_ensure_file_shares_soft_delete_is_enabled(Check): + def execute(self) -> list: + findings = [] + for subscription, storage_accounts in storage_client.storage_accounts.items(): + for storage_account in storage_accounts: + if getattr(storage_account, "file_service_properties", None): + report = Check_Report_Azure( + metadata=self.metadata(), + resource=storage_account.file_service_properties, + ) + report.subscription = subscription + report.resource_name = storage_account.name + report.location = storage_account.location + + if ( + storage_account.file_service_properties.share_delete_retention_policy.enabled + ): + report.status = "PASS" + report.status_extended = f"File share soft delete is enabled for storage account {storage_account.name} with a retention period of {storage_account.file_service_properties.share_delete_retention_policy.days} days." + else: + report.status = "FAIL" + report.status_extended = f"File share soft delete is not enabled for storage account {storage_account.name}." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/storage/storage_service.py b/prowler/providers/azure/services/storage/storage_service.py index b0aac96ec0..409a57f9f5 100644 --- a/prowler/providers/azure/services/storage/storage_service.py +++ b/prowler/providers/azure/services/storage/storage_service.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from typing import List, Optional from azure.mgmt.storage import StorageManagementClient +from pydantic import BaseModel from prowler.lib.logger import logger from prowler.providers.azure.azure_provider import AzureProvider @@ -122,6 +123,47 @@ class Storage(AzureService): f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) +<<<<<<< HEAD +======= + def _get_file_share_properties(self): + logger.info("Storage - Getting file share properties...") + for subscription, accounts in self.storage_accounts.items(): + client = self.clients[subscription] + for account in accounts: + try: + file_service_properties = ( + client.file_services.get_service_properties( + account.resouce_group_name, account.name + ) + ) + share_delete_retention_policy = getattr( + file_service_properties, + "share_delete_retention_policy", + None, + ) + account.file_service_properties = FileServiceProperties( + id=file_service_properties.id, + name=file_service_properties.name, + type=file_service_properties.type, + share_delete_retention_policy=DeleteRetentionPolicy( + enabled=getattr( + share_delete_retention_policy, + "enabled", + False, + ), + days=getattr( + share_delete_retention_policy, + "days", + 0, + ), + ), + ) + except Exception as error: + logger.error( + f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) @dataclass class DeleteRetentionPolicy: @@ -151,6 +193,27 @@ class PrivateEndpointConnection: type: str +<<<<<<< HEAD +======= +class ReplicationSettings(Enum): + STANDARD_LRS = "Standard_LRS" + STANDARD_GRS = "Standard_GRS" + STANDARD_RAGRS = "Standard_RAGRS" + STANDARD_ZRS = "Standard_ZRS" + PREMIUM_LRS = "Premium_LRS" + PREMIUM_ZRS = "Premium_ZRS" + STANDARD_GZRS = "Standard_GZRS" + STANDARD_RAGZRS = "Standard_RAGZRS" + + +class FileServiceProperties(BaseModel): + id: str + name: str + type: str + share_delete_retention_policy: DeleteRetentionPolicy + + +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) @dataclass class Account: id: str @@ -166,3 +229,8 @@ class Account: key_expiration_period_in_days: str location: str blob_properties: Optional[BlobProperties] = None +<<<<<<< HEAD +======= + default_to_entra_authorization: bool = False + file_service_properties: Optional[FileServiceProperties] = None +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) diff --git a/tests/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled_test.py b/tests/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled_test.py new file mode 100644 index 0000000000..61baac34b6 --- /dev/null +++ b/tests/providers/azure/services/storage/storage_ensure_file_shares_soft_delete_is_enabled/storage_ensure_file_shares_soft_delete_is_enabled_test.py @@ -0,0 +1,194 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.storage.storage_service import ( + Account, + DeleteRetentionPolicy, + FileServiceProperties, +) +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_storage_ensure_file_shares_soft_delete_is_enabled: + def test_no_storage_accounts(self): + storage_client = mock.MagicMock + storage_client.storage_accounts = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import ( + storage_ensure_file_shares_soft_delete_is_enabled, + ) + + check = storage_ensure_file_shares_soft_delete_is_enabled() + result = check.execute() + assert len(result) == 0 + + def test_storage_account_no_file_properties(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + storage_client = mock.MagicMock + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name=None, + enable_https_traffic_only=False, + infrastructure_encryption=False, + allow_blob_public_access=None, + network_rule_set=None, + encryption_type="None", + minimum_tls_version=None, + key_expiration_period_in_days=None, + location="westeurope", + private_endpoint_connections=None, + file_service_properties=None, + ) + ] + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import ( + storage_ensure_file_shares_soft_delete_is_enabled, + ) + + check = storage_ensure_file_shares_soft_delete_is_enabled() + result = check.execute() + assert len(result) == 0 + + def test_file_share_soft_delete_disabled(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + storage_client = mock.MagicMock + retention_policy = DeleteRetentionPolicy(enabled=False, days=0) + file_service_properties = FileServiceProperties( + id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/prowler-resource-group/providers/Microsoft.Storage/storageAccounts/{storage_account_name}/fileServices/default", + name="default", + type="Microsoft.Storage/storageAccounts/fileServices", + share_delete_retention_policy=retention_policy, + ) + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name=None, + enable_https_traffic_only=False, + infrastructure_encryption=False, + allow_blob_public_access=None, + network_rule_set=None, + encryption_type="None", + minimum_tls_version=None, + key_expiration_period_in_days=None, + location="westeurope", + private_endpoint_connections=None, + file_service_properties=file_service_properties, + ) + ] + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import ( + storage_ensure_file_shares_soft_delete_is_enabled, + ) + + check = storage_ensure_file_shares_soft_delete_is_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"File share soft delete is not enabled for storage account {storage_account_name}." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == storage_account_name + assert result[0].resource_id == file_service_properties.id + assert result[0].location == "westeurope" + + def test_file_share_soft_delete_enabled(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + storage_client = mock.MagicMock + retention_policy = DeleteRetentionPolicy(enabled=True, days=7) + file_service_properties = FileServiceProperties( + id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/prowler-resource-group/providers/Microsoft.Storage/storageAccounts/{storage_account_name}/fileServices/default", + name="default", + type="Microsoft.Storage/storageAccounts/fileServices", + share_delete_retention_policy=retention_policy, + ) + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name=None, + enable_https_traffic_only=False, + infrastructure_encryption=False, + allow_blob_public_access=None, + network_rule_set=None, + encryption_type="None", + minimum_tls_version=None, + key_expiration_period_in_days=None, + location="westeurope", + private_endpoint_connections=None, + file_service_properties=file_service_properties, + ) + ] + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import ( + storage_ensure_file_shares_soft_delete_is_enabled, + ) + + check = storage_ensure_file_shares_soft_delete_is_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"File share soft delete is enabled for storage account {storage_account_name} with a retention period of {retention_policy.days} days." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == storage_account_name + assert result[0].resource_id == file_service_properties.id + assert result[0].location == "westeurope" diff --git a/tests/providers/azure/services/storage/storage_service_test.py b/tests/providers/azure/services/storage/storage_service_test.py index 9ea5a57ad5..01aea03207 100644 --- a/tests/providers/azure/services/storage/storage_service_test.py +++ b/tests/providers/azure/services/storage/storage_service_test.py @@ -3,6 +3,12 @@ from unittest.mock import patch from prowler.providers.azure.services.storage.storage_service import ( Account, BlobProperties, +<<<<<<< HEAD +======= + DeleteRetentionPolicy, + FileServiceProperties, + ReplicationSettings, +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) Storage, ) from tests.providers.azure.azure_fixtures import ( @@ -19,6 +25,16 @@ def mock_storage_get_storage_accounts(_): default_service_version=None, container_delete_retention_policy=None, ) +<<<<<<< HEAD +======= + retention_policy = DeleteRetentionPolicy(enabled=True, days=7) + file_service_properties = FileServiceProperties( + id="id", + name="name", + type="type", + share_delete_retention_policy=retention_policy, + ) +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) return { AZURE_SUBSCRIPTION_ID: [ Account( @@ -35,6 +51,14 @@ def mock_storage_get_storage_accounts(_): private_endpoint_connections=None, location="westeurope", blob_properties=blob_properties, +<<<<<<< HEAD +======= + default_to_entra_authorization=True, + replication_settings=ReplicationSettings.STANDARD_LRS, + allow_cross_tenant_replication=True, + allow_shared_key_access=True, + file_service_properties=file_service_properties, +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087)) ) ] } @@ -143,3 +167,16 @@ class Test_Storage_Service: ].blob_properties.container_delete_retention_policy is None ) +<<<<<<< HEAD +======= + + def test_get_file_service_properties(self): + storage = Storage(set_mocked_azure_provider()) + account = storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0] + assert hasattr(account, "file_service_properties") + assert ( + account.file_service_properties.share_delete_retention_policy.enabled + is True + ) + assert account.file_service_properties.share_delete_retention_policy.days == 7 +>>>>>>> e0465f2aa (fix(azure): consolidate file share properties to the storage account level (#8087))