diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 0d078ff2a6..393a1d50d2 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to the **Prowler SDK** are documented in this file. ## [v5.8.0] (Prowler UNRELEASED) ### Added + +- Add `storage_cross_tenant_replication_disabled` check for Azure provider. [(#7977)](https://github.com/prowler-cloud/prowler/pull/7977) - CIS 1.11 compliance framework for Kubernetes [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) - Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720) - Weight for Prowler ThreatScore scoring [(#7795)](https://github.com/prowler-cloud/prowler/pull/7795) diff --git a/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/__init__.py b/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled.metadata.json b/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled.metadata.json new file mode 100644 index 0000000000..0ece0d3823 --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "storage_cross_tenant_replication_disabled", + "CheckTitle": "Ensure cross-tenant replication is disabled", + "CheckType": [], + "ServiceName": "storage", + "SubServiceName": "account", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "AzureStorageAccount", + "Description": "Ensure that cross-tenant replication is not enabled on Azure Storage Accounts to prevent unintended replication of data across tenant boundaries.", + "Risk": "If cross-tenant replication is enabled, sensitive data could be inadvertently replicated across tenants, increasing the risk of data leakage, unauthorized access, or non-compliance with data governance and privacy policies.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/storage/blobs/object-replication-prevent-cross-tenant-policies?tabs=portal", + "Remediation": { + "Code": { + "CLI": "az storage account update --name --resource-group --default-to-oauth-authentication true --allow-cross-tenant-replication false", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/StorageAccounts/disable-cross-tenant-replication.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable Cross Tenant Replication on storage accounts to ensure that data remains within tenant boundaries unless explicitly shared, reducing the risk of data leakage and unauthorized access.", + "Url": "https://learn.microsoft.com/en-us/azure/storage/blobs/object-replication-prevent-cross-tenant-policies?tabs=portal" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled.py b/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled.py new file mode 100644 index 0000000000..65ed50545e --- /dev/null +++ b/prowler/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled.py @@ -0,0 +1,34 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.storage.storage_client import storage_client + + +class storage_cross_tenant_replication_disabled(Check): + """Check if cross-tenant replication is disabled. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> Check_Report_Azure: + """Execute the check for cross-tenant replication. + + This method checks if cross-tenant replication is disabled. If it is, the check passes; otherwise, it fails. + + Returns: + Check_Report_Azure: A report containing the result of the check. + """ + findings = [] + for subscription, storage_accounts in storage_client.storage_accounts.items(): + for storage_account in storage_accounts: + report = Check_Report_Azure( + metadata=self.metadata(), resource=storage_account + ) + report.subscription = subscription + if not storage_account.allow_cross_tenant_replication: + report.status = "PASS" + report.status_extended = f"Storage account {storage_account.name} from subscription {subscription} has cross-tenant replication disabled." + else: + report.status = "FAIL" + report.status_extended = f"Storage account {storage_account.name} from subscription {subscription} has cross-tenant replication enabled." + 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 dd49943400..f6df697ff1 100644 --- a/prowler/providers/azure/services/storage/storage_service.py +++ b/prowler/providers/azure/services/storage/storage_service.py @@ -68,6 +68,9 @@ class Storage(AzureService): ], key_expiration_period_in_days=key_expiration_period_in_days, location=storage_account.location, + allow_cross_tenant_replication=getattr( + storage_account, "allow_cross_tenant_replication", True + ), allow_shared_key_access=getattr( storage_account, "allow_shared_key_access", True ), @@ -222,6 +225,7 @@ class Account: private_endpoint_connections: List[PrivateEndpointConnection] key_expiration_period_in_days: str location: str + allow_cross_tenant_replication: bool = True allow_shared_key_access: bool = True blob_properties: Optional[BlobProperties] = None file_shares: list = None diff --git a/tests/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled_test.py b/tests/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled_test.py new file mode 100644 index 0000000000..803856572a --- /dev/null +++ b/tests/providers/azure/services/storage/storage_cross_tenant_replication_disabled/storage_cross_tenant_replication_disabled_test.py @@ -0,0 +1,134 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.storage.storage_service import Account +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_storage_cross_tenant_replication_disabled: + 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_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled import ( + storage_cross_tenant_replication_disabled, + ) + + check = storage_cross_tenant_replication_disabled() + result = check.execute() + assert len(result) == 0 + + def test_storage_account_cross_tenant_replication_enabled(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, + allow_cross_tenant_replication=True, + ) + ] + } + + 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_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled import ( + storage_cross_tenant_replication_disabled, + ) + + check = storage_cross_tenant_replication_disabled() + 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} has cross-tenant replication enabled." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == storage_account_name + assert result[0].resource_id == storage_account_id + assert result[0].location == "westeurope" + + def test_storage_account_cross_tenant_replication_disabled(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, + allow_cross_tenant_replication=False, + ) + ] + } + + 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_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled.storage_client", + new=storage_client, + ), + ): + from prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled import ( + storage_cross_tenant_replication_disabled, + ) + + check = storage_cross_tenant_replication_disabled() + 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 cross-tenant replication disabled." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == storage_account_name + assert result[0].resource_id == storage_account_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 09057b0463..0b42f88e33 100644 --- a/tests/providers/azure/services/storage/storage_service_test.py +++ b/tests/providers/azure/services/storage/storage_service_test.py @@ -40,6 +40,7 @@ def mock_storage_get_storage_accounts(_): private_endpoint_connections=None, location="westeurope", blob_properties=blob_properties, + allow_cross_tenant_replication=True, allow_shared_key_access=True, file_shares=file_shares, ) @@ -117,6 +118,12 @@ class Test_Storage_Service: default_service_version=None, container_delete_retention_policy=None, ) + assert ( + storage.storage_accounts[AZURE_SUBSCRIPTION_ID][ + 0 + ].allow_cross_tenant_replication + is True + ) assert ( storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].allow_shared_key_access is True