From a319f807016b5169769520c307e2c5d090da447f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Wed, 9 Jul 2025 11:28:00 +0200 Subject: [PATCH] feat(storage): add new check `storage_smb_protocol_version_is_latest` (#8128) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 1 + .../azure/services/storage/lib/__init__.py | 0 .../azure/services/storage/lib/constants.py | 3 + .../azure/services/storage/storage_service.py | 22 +- .../__init__.py | 0 ...b_protocol_version_is_latest.metadata.json | 30 ++ .../storage_smb_protocol_version_is_latest.py | 48 +++ ...file_shares_soft_delete_is_enabled_test.py | 8 +- .../services/storage/storage_service_test.py | 12 +- ...l_encryption_with_secure_algorithm_test.py | 8 +- ...age_smb_protocol_version_is_latest_test.py | 303 ++++++++++++++++++ 11 files changed, 428 insertions(+), 7 deletions(-) create mode 100644 prowler/providers/azure/services/storage/lib/__init__.py create mode 100644 prowler/providers/azure/services/storage/lib/constants.py create mode 100644 prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/__init__.py create mode 100644 prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.metadata.json create mode 100644 prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.py create mode 100644 tests/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f51a8c6f4d..52e44543d5 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Added - `storage_smb_channel_encryption_with_secure_algorithm` check for Azure provider [(#8123)](https://github.com/prowler-cloud/prowler/pull/8123) +- `storage_smb_protocol_version_is_latest` check for Azure provider [(#8128)](https://github.com/prowler-cloud/prowler/pull/8128) - `vm_backup_enabled` check for Azure provider [(#8182)](https://github.com/prowler-cloud/prowler/pull/8182) - `vm_linux_enforce_ssh_authentication` check for Azure provider [(#8149)](https://github.com/prowler-cloud/prowler/pull/8149) - `vm_ensure_using_approved_images` check for Azure provider [(#8168)](https://github.com/prowler-cloud/prowler/pull/8168) diff --git a/prowler/providers/azure/services/storage/lib/__init__.py b/prowler/providers/azure/services/storage/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/storage/lib/constants.py b/prowler/providers/azure/services/storage/lib/constants.py new file mode 100644 index 0000000000..225d7bc5e2 --- /dev/null +++ b/prowler/providers/azure/services/storage/lib/constants.py @@ -0,0 +1,3 @@ +"""Constants for the storage service.""" + +LATEST_SMB_VERSION = "SMB3.1.1" diff --git a/prowler/providers/azure/services/storage/storage_service.py b/prowler/providers/azure/services/storage/storage_service.py index 633d1b5a54..887fb80b7b 100644 --- a/prowler/providers/azure/services/storage/storage_service.py +++ b/prowler/providers/azure/services/storage/storage_service.py @@ -172,6 +172,20 @@ class Storage(AzureService): None, ) + smb_supported_versions_raw = getattr( + getattr( + getattr( + file_service_properties, + "protocol_settings", + None, + ), + "smb", + None, + ), + "versions", + None, + ) + account.file_service_properties = FileServiceProperties( id=file_service_properties.id, name=file_service_properties.name, @@ -193,7 +207,12 @@ class Storage(AzureService): smb_channel_encryption_raw.rstrip(";").split(";") if smb_channel_encryption_raw else [] - ) + ), + supported_versions=( + smb_supported_versions_raw.rstrip(";").split(";") + if smb_supported_versions_raw + else [] + ), ), ) except Exception as error: @@ -240,6 +259,7 @@ class ReplicationSettings(Enum): class SMBProtocolSettings(BaseModel): channel_encryption: list[str] + supported_versions: list[str] class FileServiceProperties(BaseModel): diff --git a/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/__init__.py b/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.metadata.json b/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.metadata.json new file mode 100644 index 0000000000..0dea47ba46 --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "storage_smb_protocol_version_is_latest", + "CheckTitle": "Ensure SMB protocol version for file shares is set to the latest version.", + "CheckType": [], + "ServiceName": "storage", + "SubServiceName": "", + "ResourceIdTemplate": "/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/fileServices/default", + "Severity": "medium", + "ResourceType": "AzureStorageAccount", + "Description": "Ensure that SMB file shares are configured to use only the latest SMB protocol version.", + "Risk": "Allowing older SMB protocol versions may expose file shares to known vulnerabilities and security risks.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/storage/files/files-smb-protocol#smb-security-settings", + "Remediation": { + "Code": { + "CLI": "az storage account file-service-properties update --resource-group --account-name --versions ", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Configure your Azure Storage Account file shares to allow only the latest SMB protocol version.", + "Url": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/StorageAccounts/latest-smb-protocol-version.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.py b/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.py new file mode 100644 index 0000000000..19f2d37765 --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.storage.lib.constants import LATEST_SMB_VERSION +from prowler.providers.azure.services.storage.storage_client import storage_client + + +class storage_smb_protocol_version_is_latest(Check): + """ + Ensure SMB protocol version for file shares is set to the latest version. + + This check evaluates whether SMB file shares are configured to use only the latest SMB protocol version. + - PASS: Storage account allows only the latest SMB protocol version for file shares. + - FAIL: Storage account allows other SMB protocol versions for file shares. + """ + + def execute(self) -> list[Check_Report_Azure]: + findings = [] + + for subscription, storage_accounts in storage_client.storage_accounts.items(): + for account in storage_accounts: + if getattr(account, "file_service_properties", None) and getattr( + account.file_service_properties.smb_protocol_settings, + "supported_versions", + None, + ): + report = Check_Report_Azure( + metadata=self.metadata(), + resource=account.file_service_properties, + ) + report.subscription = subscription + report.resource_name = account.name + report.location = account.location + if ( + len( + account.file_service_properties.smb_protocol_settings.supported_versions + ) + == 1 + and account.file_service_properties.smb_protocol_settings.supported_versions[ + 0 + ] + == LATEST_SMB_VERSION + ): + report.status = "PASS" + report.status_extended = f"Storage account {account.name} from subscription {subscription} allows only the latest SMB protocol version ({LATEST_SMB_VERSION}) for file shares." + else: + report.status = "FAIL" + report.status_extended = f"Storage account {account.name} from subscription {subscription} allows SMB protocol versions: {', '.join(account.file_service_properties.smb_protocol_settings.supported_versions) if account.file_service_properties.smb_protocol_settings.supported_versions else 'None'}. Only the latest SMB protocol version ({LATEST_SMB_VERSION}) should be allowed." + findings.append(report) + return findings 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 index 733fb89dc7..e2c97b7e2e 100644 --- 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 @@ -91,7 +91,9 @@ class Test_storage_ensure_file_shares_soft_delete_is_enabled: name="default", type="Microsoft.Storage/storageAccounts/fileServices", share_delete_retention_policy=retention_policy, - smb_protocol_settings=SMBProtocolSettings(channel_encryption=[]), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=[] + ), ) storage_client.storage_accounts = { AZURE_SUBSCRIPTION_ID: [ @@ -152,7 +154,9 @@ class Test_storage_ensure_file_shares_soft_delete_is_enabled: name="default", type="Microsoft.Storage/storageAccounts/fileServices", share_delete_retention_policy=retention_policy, - smb_protocol_settings=SMBProtocolSettings(channel_encryption=[]), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=[] + ), ) storage_client.storage_accounts = { AZURE_SUBSCRIPTION_ID: [ diff --git a/tests/providers/azure/services/storage/storage_service_test.py b/tests/providers/azure/services/storage/storage_service_test.py index 80d1e5c250..6eb21052a2 100644 --- a/tests/providers/azure/services/storage/storage_service_test.py +++ b/tests/providers/azure/services/storage/storage_service_test.py @@ -30,7 +30,9 @@ def mock_storage_get_storage_accounts(_): name="name", type="type", share_delete_retention_policy=retention_policy, - smb_protocol_settings=SMBProtocolSettings(channel_encryption=[]), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=[] + ), ) return { AZURE_SUBSCRIPTION_ID: [ @@ -215,3 +217,11 @@ class Test_Storage_Service: is True ) assert account.file_service_properties.share_delete_retention_policy.days == 7 + assert ( + account.file_service_properties.smb_protocol_settings.channel_encryption + == [] + ) + assert ( + account.file_service_properties.smb_protocol_settings.supported_versions + == [] + ) diff --git a/tests/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm_test.py b/tests/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm_test.py index 1374bb897e..db82c09df4 100644 --- a/tests/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm_test.py +++ b/tests/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm_test.py @@ -87,7 +87,9 @@ class Test_storage_smb_channel_encryption_with_secure_algorithm: name="fs1", type="type1", share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), - smb_protocol_settings=SMBProtocolSettings(channel_encryption=[]), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=[] + ), ) storage_client = mock.MagicMock() storage_client.storage_accounts = { @@ -142,7 +144,7 @@ class Test_storage_smb_channel_encryption_with_secure_algorithm: type="type1", share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), smb_protocol_settings=SMBProtocolSettings( - channel_encryption=["AES-128-GCM"] + channel_encryption=["AES-128-GCM"], supported_versions=[] ), ) storage_client = mock.MagicMock() @@ -198,7 +200,7 @@ class Test_storage_smb_channel_encryption_with_secure_algorithm: type="type1", share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), smb_protocol_settings=SMBProtocolSettings( - channel_encryption=["AES-256-GCM"] + channel_encryption=["AES-256-GCM"], supported_versions=[] ), ) storage_client = mock.MagicMock() diff --git a/tests/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest_test.py b/tests/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest_test.py new file mode 100644 index 0000000000..4194c7ae55 --- /dev/null +++ b/tests/providers/azure/services/storage/storage_smb_protocol_version_is_latest/storage_smb_protocol_version_is_latest_test.py @@ -0,0 +1,303 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.storage.storage_service import ( + Account, + DeleteRetentionPolicy, + FileServiceProperties, + NetworkRuleSet, + SMBProtocolSettings, +) +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_storage_smb_protocol_version_is_latest: + 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_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest import ( + storage_smb_protocol_version_is_latest, + ) + + check = storage_smb_protocol_version_is_latest() + result = check.execute() + assert len(result) == 0 + + def test_no_file_service_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="rg", + enable_https_traffic_only=True, + infrastructure_encryption=True, + allow_blob_public_access=False, + network_rule_set=NetworkRuleSet( + bypass="AzureServices", default_action="Allow" + ), + encryption_type="type", + minimum_tls_version="TLS1_2", + private_endpoint_connections=[], + key_expiration_period_in_days=None, + location="eastus", + 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_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest import ( + storage_smb_protocol_version_is_latest, + ) + + check = storage_smb_protocol_version_is_latest() + result = check.execute() + assert len(result) == 0 + + def test_only_latest_smb_protocol_version(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id=storage_account_id, + name="default", + type="type", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=["SMB3.1.1"] + ), + ) + storage_client = mock.MagicMock() + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name="rg", + enable_https_traffic_only=True, + infrastructure_encryption=True, + allow_blob_public_access=False, + network_rule_set=NetworkRuleSet( + bypass="AzureServices", default_action="Allow" + ), + encryption_type="type", + minimum_tls_version="TLS1_2", + private_endpoint_connections=[], + key_expiration_period_in_days=None, + location="eastus", + 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_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest import ( + storage_smb_protocol_version_is_latest, + ) + + check = storage_smb_protocol_version_is_latest() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} allows only the latest SMB protocol version (SMB3.1.1) for file shares." + in result[0].status_extended + ) + + def test_multiple_smb_protocol_versions(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id=storage_account_id, + name="default", + type="type", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=["SMB2.1", "SMB3.1.1"] + ), + ) + storage_client = mock.MagicMock() + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name="rg", + enable_https_traffic_only=True, + infrastructure_encryption=True, + allow_blob_public_access=False, + network_rule_set=NetworkRuleSet( + bypass="AzureServices", default_action="Allow" + ), + encryption_type="type", + minimum_tls_version="TLS1_2", + private_endpoint_connections=[], + key_expiration_period_in_days=None, + location="eastus", + 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_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest import ( + storage_smb_protocol_version_is_latest, + ) + + check = storage_smb_protocol_version_is_latest() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} allows SMB protocol versions: SMB2.1, SMB3.1.1. Only the latest SMB protocol version (SMB3.1.1) should be allowed." + in result[0].status_extended + ) + + def test_no_smb_protocol_versions(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id=storage_account_id, + name="default", + type="type", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=[] + ), + ) + storage_client = mock.MagicMock() + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name="rg", + enable_https_traffic_only=True, + infrastructure_encryption=True, + allow_blob_public_access=False, + network_rule_set=NetworkRuleSet( + bypass="AzureServices", default_action="Allow" + ), + encryption_type="type", + minimum_tls_version="TLS1_2", + private_endpoint_connections=[], + key_expiration_period_in_days=None, + location="eastus", + 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_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest import ( + storage_smb_protocol_version_is_latest, + ) + + check = storage_smb_protocol_version_is_latest() + result = check.execute() + assert len(result) == 0 + + def test_multiple_required_versions_custom_config(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id=storage_account_id, + name="default", + type="type", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=[], supported_versions=["SMB3.1.1", "SMB3.0"] + ), + ) + storage_client = mock.MagicMock() + storage_client.storage_accounts = { + AZURE_SUBSCRIPTION_ID: [ + Account( + id=storage_account_id, + name=storage_account_name, + resouce_group_name="rg", + enable_https_traffic_only=True, + infrastructure_encryption=True, + allow_blob_public_access=False, + network_rule_set=NetworkRuleSet( + bypass="AzureServices", default_action="Allow" + ), + encryption_type="type", + minimum_tls_version="TLS1_2", + private_endpoint_connections=[], + key_expiration_period_in_days=None, + location="eastus", + 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_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_protocol_version_is_latest.storage_smb_protocol_version_is_latest import ( + storage_smb_protocol_version_is_latest, + ) + + check = storage_smb_protocol_version_is_latest() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} allows SMB protocol versions: SMB3.1.1, SMB3.0. Only the latest SMB protocol version (SMB3.1.1) should be allowed." + in result[0].status_extended + )