From 6f3112f75420b2d95dd9aa731b2bc5f6af605244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Fri, 4 Jul 2025 09:26:33 +0200 Subject: [PATCH] feat(storage): add new check `storage_smb_channel_encryption_with_secure_algorithm ` (#8123) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 1 + .../azure/services/storage/storage_service.py | 27 ++ .../__init__.py | 0 ...yption_with_secure_algorithm.metadata.json | 30 +++ ...hannel_encryption_with_secure_algorithm.py | 51 ++++ tests/providers/azure/azure_provider_test.py | 9 - ...file_shares_soft_delete_is_enabled_test.py | 3 + .../services/storage/storage_service_test.py | 2 + ...l_encryption_with_secure_algorithm_test.py | 237 ++++++++++++++++++ 9 files changed, 351 insertions(+), 9 deletions(-) create mode 100644 prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/__init__.py create mode 100644 prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.metadata.json create mode 100644 prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.py create mode 100644 tests/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 944d7747f9..d122c8a292 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ## [v5.9.0] (Prowler UNRELEASED) ### Added +- `storage_smb_channel_encryption_with_secure_algorithm` check for Azure provider [(#8123)](https://github.com/prowler-cloud/prowler/pull/8123) ### Changed diff --git a/prowler/providers/azure/services/storage/storage_service.py b/prowler/providers/azure/services/storage/storage_service.py index 34c5ff1143..67fcc70e07 100644 --- a/prowler/providers/azure/services/storage/storage_service.py +++ b/prowler/providers/azure/services/storage/storage_service.py @@ -158,6 +158,21 @@ class Storage(AzureService): "share_delete_retention_policy", None, ) + + smb_channel_encryption_raw = getattr( + getattr( + getattr( + file_service_properties, + "protocol_settings", + None, + ), + "smb", + None, + ), + "channel_encryption", + None, + ) + account.file_service_properties = FileServiceProperties( id=file_service_properties.id, name=file_service_properties.name, @@ -174,6 +189,13 @@ class Storage(AzureService): 0, ), ), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=( + smb_channel_encryption_raw.rstrip(";").split(";") + if smb_channel_encryption_raw + else [] + ) + ), ) except Exception as error: logger.error( @@ -221,11 +243,16 @@ class ReplicationSettings(Enum): STANDARD_RAGZRS = "Standard_RAGZRS" +class SMBProtocolSettings(BaseModel): + channel_encryption: list[str] + + class FileServiceProperties(BaseModel): id: str name: str type: str share_delete_retention_policy: DeleteRetentionPolicy + smb_protocol_settings: SMBProtocolSettings @dataclass diff --git a/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/__init__.py b/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.metadata.json b/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.metadata.json new file mode 100644 index 0000000000..02ea0ab141 --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "storage_smb_channel_encryption_with_secure_algorithm", + "CheckTitle": "Ensure SMB channel encryption uses a secure algorithm for SMB file shares", + "CheckType": [], + "ServiceName": "storage", + "SubServiceName": "", + "ResourceIdTemplate": "/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/fileServices/default", + "Severity": "medium", + "ResourceType": "AzureStorageAccount", + "Description": "Implement SMB channel encryption with a secure algorithm for SMB file shares to ensure data confidentiality and integrity in transit.", + "Risk": "Not using the recommended SMB channel encryption may expose data transmitted over SMB channels to unauthorized interception and tampering.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-files#recommendations-for-smb-file-shares", + "Remediation": { + "Code": { + "CLI": "az storage account file-service-properties update --resource-group --account-name --channel-encryption AES-256-GCM", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Use the portal, CLI or PowerShell to set the SMB channel encryption to a secure algorithm.", + "Url": "https://learn.microsoft.com/en-us/azure/storage/files/files-smb-protocol?tabs=azure-portal#smb-security-settings" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "This check passes if SMB channel encryption is set to a secure algorithm." +} diff --git a/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.py b/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.py new file mode 100644 index 0000000000..c8e9f1da3c --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm.py @@ -0,0 +1,51 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.storage.storage_client import storage_client + +SECURE_ENCRYPTION_ALGORITHMS = ["AES-256-GCM"] + + +class storage_smb_channel_encryption_with_secure_algorithm(Check): + """ + Ensure SMB channel encryption for file shares is set to the recommended algorithm (AES-256-GCM or higher). + + This check evaluates whether SMB file shares are configured to use only the recommended SMB channel encryption algorithms. + - PASS: Storage account has the recommended SMB channel encryption (AES-256-GCM or higher) enabled for file shares. + - FAIL: Storage account does not have the recommended SMB channel encryption enabled for file shares or uses an unsupported algorithm. + """ + + def execute(self) -> list[Check_Report_Azure]: + findings = [] + for subscription, storage_accounts in storage_client.storage_accounts.items(): + for account in storage_accounts: + if account.file_service_properties: + pretty_current_algorithms = ( + ", ".join( + account.file_service_properties.smb_protocol_settings.channel_encryption + ) + if account.file_service_properties.smb_protocol_settings.channel_encryption + else "none" + ) + report = Check_Report_Azure( + metadata=self.metadata(), + resource=account.file_service_properties, + ) + report.subscription = subscription + report.resource_name = account.name + + if ( + not account.file_service_properties.smb_protocol_settings.channel_encryption + ): + report.status = "FAIL" + report.status_extended = f"Storage account {account.name} from subscription {subscription} does not have SMB channel encryption enabled for file shares." + elif any( + algorithm in SECURE_ENCRYPTION_ALGORITHMS + for algorithm in account.file_service_properties.smb_protocol_settings.channel_encryption + ): + report.status = "PASS" + report.status_extended = f"Storage account {account.name} from subscription {subscription} has a secure algorithm for SMB channel encryption ({', '.join(SECURE_ENCRYPTION_ALGORITHMS)}) enabled for file shares since it supports {pretty_current_algorithms}." + else: + report.status = "FAIL" + report.status_extended = f"Storage account {account.name} from subscription {subscription} does not have SMB channel encryption with a secure algorithm for file shares since it supports {pretty_current_algorithms}." + + findings.append(report) + return findings diff --git a/tests/providers/azure/azure_provider_test.py b/tests/providers/azure/azure_provider_test.py index e5e93bad6b..8967142bc5 100644 --- a/tests/providers/azure/azure_provider_test.py +++ b/tests/providers/azure/azure_provider_test.py @@ -110,7 +110,6 @@ class TestAzureProvider: return_value={}, ), ): - with pytest.raises(AzureNoAuthenticationMethodError) as exception: _ = AzureProvider( az_cli_auth, @@ -151,7 +150,6 @@ class TestAzureProvider: return_value={}, ), ): - with pytest.raises(AzureBrowserAuthNoTenantIDError) as exception: _ = AzureProvider( az_cli_auth, @@ -193,7 +191,6 @@ class TestAzureProvider: return_value={}, ), ): - with pytest.raises(AzureTenantIDNoBrowserAuthError) as exception: _ = AzureProvider( az_cli_auth, @@ -224,7 +221,6 @@ class TestAzureProvider: "prowler.providers.azure.azure_provider.SubscriptionClient" ) as mock_resource_client, ): - # Mock the return value of DefaultAzureCredential mock_credentials = MagicMock() mock_credentials.get_token.return_value = AccessToken( @@ -266,7 +262,6 @@ class TestAzureProvider: "prowler.providers.azure.azure_provider.AzureProvider.validate_static_credentials" ) as mock_validate_static_credentials, ): - # Mock the return value of DefaultAzureCredential mock_credentials = MagicMock() mock_credentials.get_token.return_value = AccessToken( @@ -317,7 +312,6 @@ class TestAzureProvider: "prowler.providers.azure.azure_provider.AzureProvider.validate_static_credentials" ) as mock_validate_static_credentials, ): - # Mock the return value of DefaultAzureCredential mock_default_credential.return_value = { "client_id": str(uuid4()), @@ -368,7 +362,6 @@ class TestAzureProvider: "prowler.providers.azure.azure_provider.AzureProvider.validate_static_credentials" ) as mock_validate_static_credentials, ): - # Mock the return value of DefaultAzureCredential mock_default_credential.return_value = { "client_id": str(uuid4()), @@ -442,7 +435,6 @@ class TestAzureProvider: "prowler.providers.azure.azure_provider.AzureProvider.setup_session" ) as mock_setup_session, ): - mock_setup_session.side_effect = AzureHTTPResponseError( file="test_file", original_exception="Simulated HttpResponseError" ) @@ -463,7 +455,6 @@ class TestAzureProvider: with patch( "prowler.providers.azure.azure_provider.AzureProvider.setup_session" ) as mock_setup_session: - mock_setup_session.side_effect = Exception("Simulated Exception") with pytest.raises(Exception) as exception: 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 61baac34b6..29f64bc066 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 @@ -5,6 +5,7 @@ from prowler.providers.azure.services.storage.storage_service import ( Account, DeleteRetentionPolicy, FileServiceProperties, + SMBProtocolSettings, ) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, @@ -87,6 +88,7 @@ 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=[]), ) storage_client.storage_accounts = { AZURE_SUBSCRIPTION_ID: [ @@ -145,6 +147,7 @@ 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=[]), ) 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 a6205b789c..7b9b5d99fe 100644 --- a/tests/providers/azure/services/storage/storage_service_test.py +++ b/tests/providers/azure/services/storage/storage_service_test.py @@ -6,6 +6,7 @@ from prowler.providers.azure.services.storage.storage_service import ( DeleteRetentionPolicy, FileServiceProperties, ReplicationSettings, + SMBProtocolSettings, Storage, ) from tests.providers.azure.azure_fixtures import ( @@ -28,6 +29,7 @@ def mock_storage_get_storage_accounts(_): name="name", type="type", share_delete_retention_policy=retention_policy, + smb_protocol_settings=SMBProtocolSettings(channel_encryption=[]), ) return { AZURE_SUBSCRIPTION_ID: [ 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 new file mode 100644 index 0000000000..9b53a0fd11 --- /dev/null +++ b/tests/providers/azure/services/storage/storage_smb_channel_encryption_with_secure_algorithm/storage_smb_channel_encryption_with_secure_algorithm_test.py @@ -0,0 +1,237 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.storage.storage_service import ( + Account, + DeleteRetentionPolicy, + FileServiceProperties, + SMBProtocolSettings, +) +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_storage_smb_channel_encryption_with_secure_algorithm: + 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_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm import ( + storage_smb_channel_encryption_with_secure_algorithm, + ) + + check = storage_smb_channel_encryption_with_secure_algorithm() + 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=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_smb_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm import ( + storage_smb_channel_encryption_with_secure_algorithm, + ) + + check = storage_smb_channel_encryption_with_secure_algorithm() + result = check.execute() + assert len(result) == 0 + + def test_no_smb_protocol_settings(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id="id1", + name="fs1", + type="type1", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings(channel_encryption=[]), + ) + 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=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_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm import ( + storage_smb_channel_encryption_with_secure_algorithm, + ) + + check = storage_smb_channel_encryption_with_secure_algorithm() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} does not have SMB channel encryption enabled for file shares." + ) + + def test_not_recommended_encryption(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id="id1", + name="fs1", + type="type1", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=["AES-128-GCM"] + ), + ) + 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=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_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm import ( + storage_smb_channel_encryption_with_secure_algorithm, + ) + + check = storage_smb_channel_encryption_with_secure_algorithm() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} does not have SMB channel encryption with a secure algorithm for file shares since it supports AES-128-GCM." + ) + + def test_recommended_encryption(self): + storage_account_id = str(uuid4()) + storage_account_name = "Test Storage Account" + file_service_properties = FileServiceProperties( + id="id1", + name="fs1", + type="type1", + share_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7), + smb_protocol_settings=SMBProtocolSettings( + channel_encryption=["AES-256-GCM"] + ), + ) + 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=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_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_smb_channel_encryption_with_secure_algorithm.storage_smb_channel_encryption_with_secure_algorithm import ( + storage_smb_channel_encryption_with_secure_algorithm, + ) + + check = storage_smb_channel_encryption_with_secure_algorithm() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has a secure algorithm for SMB channel encryption (AES-256-GCM) enabled for file shares since it supports AES-256-GCM." + )