diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 86f05ee925..92bcb9034c 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -35,6 +35,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `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) --- diff --git a/prowler/providers/azure/services/databricks/databricks_service.py b/prowler/providers/azure/services/databricks/databricks_service.py index 1f35eb74ff..7920500d88 100644 --- a/prowler/providers/azure/services/databricks/databricks_service.py +++ b/prowler/providers/azure/services/databricks/databricks_service.py @@ -41,6 +41,26 @@ class Databricks(AzureService): for workspace in client.workspaces.list_by_subscription(): workspace_parameters = getattr(workspace, "parameters", None) + workspace_managed_disk_encryption = getattr( + getattr( + getattr(workspace, "encryption", None), "entities", None + ), + "managed_disk", + None, + ) + + key_vault_properties = getattr( + workspace_managed_disk_encryption, "key_vault_properties", None + ) + + if key_vault_properties: + managed_disk_encryption = ManagedDiskEncryption( + key_name=key_vault_properties.key_name, + key_version=key_vault_properties.key_version, + key_vault_uri=key_vault_properties.key_vault_uri, + ) + else: + managed_disk_encryption = None workspaces[subscription][workspace.id] = DatabricksWorkspace( id=workspace.id, @@ -55,6 +75,7 @@ class Databricks(AzureService): ) else None ), + managed_disk_encryption=managed_disk_encryption, ) except Exception as error: logger.error( @@ -63,6 +84,21 @@ class Databricks(AzureService): return workspaces +class ManagedDiskEncryption(BaseModel): + """ + Pydantic model representing the encryption settings for a workspace's managed disks. + + Attributes: + key_name: The name of the key used for encryption. + key_version: The version of the key used for encryption. + key_vault_uri: The URI of the key vault containing the key used for encryption. + """ + + key_name: str + key_version: str + key_vault_uri: str + + class DatabricksWorkspace(BaseModel): """ Pydantic model representing an Azure Databricks workspace. @@ -72,9 +108,11 @@ class DatabricksWorkspace(BaseModel): name: The name of the workspace. location: The Azure region where the workspace is deployed. custom_managed_vnet_id: The ID of the custom managed virtual network, if configured. + managed_disk_encryption: The encryption settings for the workspace's managed disks. """ id: str name: str location: str - custom_managed_vnet_id: Optional[str] + custom_managed_vnet_id: Optional[str] = None + managed_disk_encryption: Optional[ManagedDiskEncryption] = None diff --git a/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/__init__.py b/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled.metadata.json b/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled.metadata.json new file mode 100644 index 0000000000..3742060d17 --- /dev/null +++ b/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "databricks_workspace_cmk_encryption_enabled", + "CheckTitle": "Ensure Azure Databricks workspaces use customer-managed keys (CMK) for encryption at rest", + "CheckType": [], + "ServiceName": "databricks", + "SubServiceName": "workspace", + "ResourceIdTemplate": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Databricks/workspaces/{workspaceName}", + "Severity": "high", + "ResourceType": "AzureDatabricksWorkspace", + "Description": "Checks whether Azure Databricks workspaces are configured to use customer-managed keys (CMK) for encryption at rest, providing greater control over data encryption and compliance.", + "Risk": "Without CMK, organizations have less control over encryption keys, which may impact regulatory compliance and increase risk of unauthorized data access.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/databricks/security/keys/customer-managed-keys", + "Remediation": { + "Code": { + "CLI": "az databricks workspace update --name --resource-group --prepare-encryption && databricks workspace update --name --resource-group --key-source 'Microsoft.KeyVault' --key-name --key-vault --key-version ", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable customer-managed keys (CMK) for Databricks workspaces using Azure Key Vault to enhance control over data encryption, auditing, and compliance.", + "Url": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Databricks/enable-encryption-with-cmk.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Customer-managed key (CMK) encryption is only available for Databricks workspaces on the Premium tier." +} diff --git a/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled.py b/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled.py new file mode 100644 index 0000000000..a8e366f15a --- /dev/null +++ b/prowler/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled.py @@ -0,0 +1,33 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.databricks.databricks_client import ( + databricks_client, +) + + +class databricks_workspace_cmk_encryption_enabled(Check): + """ + Ensure Azure Databricks workspaces use customer-managed keys (CMK) for encryption at rest. + + This check evaluates whether each Azure Databricks workspace in the subscription is configured to use a customer-managed key (CMK) for encrypting data at rest. + + - PASS: The workspace has CMK encryption enabled (managed_disk_encryption is set). + - FAIL: The workspace does not have CMK encryption enabled. + """ + + def execute(self): + findings = [] + for subscription, workspaces in databricks_client.workspaces.items(): + for workspace in workspaces.values(): + report = Check_Report_Azure( + metadata=self.metadata(), resource=workspace + ) + report.subscription = subscription + enc = workspace.managed_disk_encryption + if enc: + report.status = "PASS" + report.status_extended = f"Databricks workspace {workspace.name} in subscription {subscription} has customer-managed key (CMK) encryption enabled with key {enc.key_vault_uri}/{enc.key_name}/{enc.key_version}." + else: + report.status = "FAIL" + report.status_extended = f"Databricks workspace {workspace.name} in subscription {subscription} does not have customer-managed key (CMK) encryption enabled." + findings.append(report) + return findings diff --git a/tests/providers/azure/services/databricks/databricks_service_test.py b/tests/providers/azure/services/databricks/databricks_service_test.py index df4cf920f0..69651f2235 100644 --- a/tests/providers/azure/services/databricks/databricks_service_test.py +++ b/tests/providers/azure/services/databricks/databricks_service_test.py @@ -3,6 +3,7 @@ from unittest.mock import patch from prowler.providers.azure.services.databricks.databricks_service import ( Databricks, DatabricksWorkspace, + ManagedDiskEncryption, ) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, @@ -18,6 +19,11 @@ def mock_databricks_get_workspaces(_): name="test-workspace", location="eastus", custom_managed_vnet_id="test-vnet-id", + managed_disk_encryption=ManagedDiskEncryption( + key_name="test-key", + key_version="test-version", + key_vault_uri="test-vault-uri", + ), ) } } @@ -48,3 +54,39 @@ class Test_Databricks_Service: assert workspace.name == "test-workspace" assert workspace.location == "eastus" assert workspace.custom_managed_vnet_id == "test-vnet-id" + assert ( + workspace.managed_disk_encryption.__class__.__name__ + == "ManagedDiskEncryption" + ) + assert workspace.managed_disk_encryption.key_name == "test-key" + assert workspace.managed_disk_encryption.key_version == "test-version" + assert workspace.managed_disk_encryption.key_vault_uri == "test-vault-uri" + + +def mock_databricks_get_workspaces_no_encryption(_): + return { + AZURE_SUBSCRIPTION_ID: { + "test-workspace-id": DatabricksWorkspace( + id="test-workspace-id", + name="test-workspace", + location="eastus", + custom_managed_vnet_id="test-vnet-id", + managed_disk_encryption=None, + ) + } + } + + +@patch( + "prowler.providers.azure.services.databricks.databricks_service.Databricks._get_workspaces", + new=mock_databricks_get_workspaces_no_encryption, +) +class Test_Databricks_Service_No_Encryption: + def test_get_workspaces_no_encryption(self): + databricks = Databricks(set_mocked_azure_provider()) + workspace = databricks.workspaces[AZURE_SUBSCRIPTION_ID]["test-workspace-id"] + assert workspace.id == "test-workspace-id" + assert workspace.name == "test-workspace" + assert workspace.location == "eastus" + assert workspace.custom_managed_vnet_id == "test-vnet-id" + assert workspace.managed_disk_encryption is None diff --git a/tests/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled_test.py b/tests/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled_test.py new file mode 100644 index 0000000000..1e145d4f80 --- /dev/null +++ b/tests/providers/azure/services/databricks/databricks_workspace_cmk_encryption_enabled/databricks_workspace_cmk_encryption_enabled_test.py @@ -0,0 +1,130 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.databricks.databricks_service import ( + DatabricksWorkspace, + ManagedDiskEncryption, +) +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_databricks_workspace_cmk_encryption_enabled: + def test_no_databricks_workspaces(self): + databricks_client = mock.MagicMock + databricks_client.workspaces = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled.databricks_client", + new=databricks_client, + ), + ): + from prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled import ( + databricks_workspace_cmk_encryption_enabled, + ) + + check = databricks_workspace_cmk_encryption_enabled() + result = check.execute() + assert len(result) == 0 + + def test_databricks_workspace_cmk_encryption_disabled(self): + workspace_id = str(uuid4()) + workspace_name = "test-workspace" + + databricks_client = mock.MagicMock + databricks_client.workspaces = { + AZURE_SUBSCRIPTION_ID: { + workspace_id: DatabricksWorkspace( + id=workspace_id, + name=workspace_name, + location="eastus", + custom_managed_vnet_id=None, + managed_disk_encryption=None, + ) + } + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled.databricks_client", + new=databricks_client, + ), + ): + from prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled import ( + databricks_workspace_cmk_encryption_enabled, + ) + + check = databricks_workspace_cmk_encryption_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Databricks workspace {workspace_name} in subscription {AZURE_SUBSCRIPTION_ID} does not have customer-managed key (CMK) encryption enabled." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == workspace_name + assert result[0].resource_id == workspace_id + assert result[0].location == "eastus" + + def test_databricks_workspace_cmk_encryption_enabled(self): + workspace_id = str(uuid4()) + workspace_name = "test-workspace" + key_name = "test-key" + key_version = "test-version" + key_vault_uri = "test-vault-uri" + + databricks_client = mock.MagicMock + databricks_client.workspaces = { + AZURE_SUBSCRIPTION_ID: { + workspace_id: DatabricksWorkspace( + id=workspace_id, + name=workspace_name, + location="eastus", + custom_managed_vnet_id=None, + managed_disk_encryption=ManagedDiskEncryption( + key_name=key_name, + key_version=key_version, + key_vault_uri=key_vault_uri, + ), + ) + } + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled.databricks_client", + new=databricks_client, + ), + ): + from prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled import ( + databricks_workspace_cmk_encryption_enabled, + ) + + check = databricks_workspace_cmk_encryption_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Databricks workspace {workspace_name} in subscription {AZURE_SUBSCRIPTION_ID} has customer-managed key (CMK) encryption enabled with key {key_vault_uri}/{key_name}/{key_version}." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == workspace_name + assert result[0].resource_id == workspace_id + assert result[0].location == "eastus"