feat(azure): add databricks_workspace_public_network_access_disabled check (#11035)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
Co-authored-by: Hugo P.Brito <hugopbrito@Hugos-MacBook-Pro.local>
This commit is contained in:
s1ns3nz0
2026-06-17 21:12:18 +09:00
committed by GitHub
parent f1a30f706a
commit 73059ffc7e
8 changed files with 252 additions and 0 deletions
+1
View File
@@ -16,6 +16,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `cosmosdb_account_backup_policy_continuous` check for Azure provider [(#11032)](https://github.com/prowler-cloud/prowler/pull/11032)
- `cosmosdb_account_minimum_tls_version` check for Azure provider, verifying Cosmos DB accounts enforce TLS 1.2 or higher for client connections [(#11033)](https://github.com/prowler-cloud/prowler/pull/11033)
- `cosmosdb_account_public_network_access_disabled` check for Azure provider, verifying Cosmos DB accounts have public network access disabled so connectivity is restricted to private endpoints or VNet service endpoints [(#11034)](https://github.com/prowler-cloud/prowler/pull/11034)
- `databricks_workspace_public_network_access_disabled` check for Azure provider, verifying Databricks workspaces have public network access disabled so connectivity is restricted to Azure Private Link private endpoints [(#11035)](https://github.com/prowler-cloud/prowler/pull/11035)
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
- TLS certificate verification in the `codepipeline_project_repo_private` check, which previously used an unverified SSL context, leaving the repository-visibility probe open to MITM tampering [(#11603)](https://github.com/prowler-cloud/prowler/pull/11603)
@@ -1411,6 +1411,7 @@
"containerregistry_not_publicly_accessible",
"cosmosdb_account_firewall_use_selected_networks",
"cosmosdb_account_public_network_access_disabled",
"databricks_workspace_public_network_access_disabled",
"network_bastion_host_exists",
"network_flow_log_captured_sent",
"network_flow_log_more_than_90_days",
@@ -66,6 +66,9 @@ class Databricks(AzureService):
id=workspace.id,
name=workspace.name,
location=workspace.location,
public_network_access=getattr(
workspace, "public_network_access", None
),
custom_managed_vnet_id=(
getattr(
workspace_parameters, "custom_virtual_network_id", None
@@ -107,6 +110,7 @@ class DatabricksWorkspace(BaseModel):
id: The unique identifier of the workspace.
name: The name of the workspace.
location: The Azure region where the workspace is deployed.
public_network_access: Whether public network access is "Enabled" or "Disabled", if configured.
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.
"""
@@ -114,5 +118,6 @@ class DatabricksWorkspace(BaseModel):
id: str
name: str
location: str
public_network_access: Optional[str] = None
custom_managed_vnet_id: Optional[str] = None
managed_disk_encryption: Optional[ManagedDiskEncryption] = None
@@ -0,0 +1,38 @@
{
"Provider": "azure",
"CheckID": "databricks_workspace_public_network_access_disabled",
"CheckTitle": "Databricks workspace has public network access disabled",
"CheckType": [],
"ServiceName": "databricks",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "microsoft.databricks/workspaces",
"ResourceGroup": "ai_ml",
"Description": "**Azure Databricks workspaces** are evaluated for **public network access**. Disabling public network access ensures the workspace is only reachable through **private endpoints**, reducing the attack surface and preventing direct exposure of the control plane and data plane to the internet.",
"Risk": "Allowing **public network access** exposes the Databricks workspace control plane and data plane to the internet. Combined with leaked **credentials**, **personal access tokens**, or unpatched vulnerabilities, this enables **unauthorized access**, **brute force**, and **data exfiltration** from any source on the internet.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/databricks/security/network/classic/private-link",
"https://learn.microsoft.com/en-us/azure/databricks/security/network/classic/private-link#step-4-disable-public-network-access",
"https://learn.microsoft.com/en-us/azure/templates/microsoft.databricks/workspaces"
],
"Remediation": {
"Code": {
"CLI": "az databricks workspace update --name <workspace_name> --resource-group <resource_group_name> --public-network-access Disabled",
"NativeIaC": "```bicep\n// Bicep: Disable public network access on a Databricks workspace\nresource workspace 'Microsoft.Databricks/workspaces@2023-02-01' = {\n name: '<example_resource_name>'\n location: resourceGroup().location\n sku: {\n name: 'premium'\n }\n properties: {\n managedResourceGroupId: '/subscriptions/<example_resource_id>/resourceGroups/<example_resource_name>'\n publicNetworkAccess: 'Disabled' // CRITICAL: Blocks all public-internet traffic to the workspace\n }\n}\n```",
"Other": "1. Sign in to the Azure portal and open your Databricks workspace\n2. In the left menu, select Networking\n3. Under Public network access, select Disabled\n4. Configure Azure Private Link private endpoints before saving so clients retain connectivity\n5. Click Save",
"Terraform": "```hcl\n# Terraform: Disable public network access on a Databricks workspace\nresource \"azurerm_databricks_workspace\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"<region>\"\n sku = \"premium\"\n\n public_network_access_enabled = false # CRITICAL: Blocks all public-internet traffic to the workspace\n}\n```"
},
"Recommendation": {
"Text": "Disable **public network access** on Databricks workspaces and require connectivity via **Azure Private Link** private endpoints. Before enforcement, validate that all application workloads have private-network connectivity, and pair this control with **VNet injection**, **secure cluster connectivity (no public IP)**, and least-privilege access to uphold **defense in depth**.",
"Url": "https://hub.prowler.com/check/databricks_workspace_public_network_access_disabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,35 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.databricks.databricks_client import (
databricks_client,
)
class databricks_workspace_public_network_access_disabled(Check):
"""
Ensure Azure Databricks workspaces have public network access disabled.
This check evaluates whether each Azure Databricks workspace in the subscription restricts connectivity to private endpoints by disabling public network access.
- PASS: The workspace has public network access disabled (public_network_access is "Disabled").
- FAIL: The workspace has public network access enabled (or the value is not set).
"""
def execute(self):
findings = []
for subscription, workspaces in databricks_client.workspaces.items():
subscription_name = databricks_client.subscriptions.get(
subscription, subscription
)
for workspace in workspaces.values():
report = Check_Report_Azure(
metadata=self.metadata(), resource=workspace
)
report.subscription = subscription
if workspace.public_network_access == "Disabled":
report.status = "PASS"
report.status_extended = f"Databricks workspace {workspace.name} in subscription {subscription_name} ({subscription}) has public network access disabled."
else:
report.status = "FAIL"
report.status_extended = f"Databricks workspace {workspace.name} in subscription {subscription_name} ({subscription}) has public network access enabled."
findings.append(report)
return findings
@@ -18,6 +18,7 @@ def mock_databricks_get_workspaces(_):
id="test-workspace-id",
name="test-workspace",
location="eastus",
public_network_access="Disabled",
custom_managed_vnet_id="test-vnet-id",
managed_disk_encryption=ManagedDiskEncryption(
key_name="test-key",
@@ -53,6 +54,7 @@ class Test_Databricks_Service:
assert workspace.id == "test-workspace-id"
assert workspace.name == "test-workspace"
assert workspace.location == "eastus"
assert workspace.public_network_access == "Disabled"
assert workspace.custom_managed_vnet_id == "test-vnet-id"
assert (
workspace.managed_disk_encryption.__class__.__name__
@@ -0,0 +1,170 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.databricks.databricks_service import (
DatabricksWorkspace,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_DISPLAY,
AZURE_SUBSCRIPTION_ID,
AZURE_SUBSCRIPTION_NAME,
set_mocked_azure_provider,
)
class Test_databricks_workspace_public_network_access_disabled:
def test_databricks_no_workspaces(self):
databricks_client = mock.MagicMock
databricks_client.subscriptions = {
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
}
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_public_network_access_disabled.databricks_workspace_public_network_access_disabled.databricks_client",
new=databricks_client,
),
):
from prowler.providers.azure.services.databricks.databricks_workspace_public_network_access_disabled.databricks_workspace_public_network_access_disabled import (
databricks_workspace_public_network_access_disabled,
)
check = databricks_workspace_public_network_access_disabled()
result = check.execute()
assert len(result) == 0
def test_databricks_workspace_public_network_access_enabled(self):
workspace_id = str(uuid4())
workspace_name = "test-workspace"
databricks_client = mock.MagicMock
databricks_client.subscriptions = {
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
}
databricks_client.workspaces = {
AZURE_SUBSCRIPTION_ID: {
workspace_id: DatabricksWorkspace(
id=workspace_id,
name=workspace_name,
location="eastus",
public_network_access="Enabled",
)
}
}
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_public_network_access_disabled.databricks_workspace_public_network_access_disabled.databricks_client",
new=databricks_client,
),
):
from prowler.providers.azure.services.databricks.databricks_workspace_public_network_access_disabled.databricks_workspace_public_network_access_disabled import (
databricks_workspace_public_network_access_disabled,
)
check = databricks_workspace_public_network_access_disabled()
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_DISPLAY} has public network access 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_public_network_access_not_set(self):
workspace_id = str(uuid4())
workspace_name = "test-workspace"
databricks_client = mock.MagicMock
databricks_client.subscriptions = {
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
}
databricks_client.workspaces = {
AZURE_SUBSCRIPTION_ID: {
workspace_id: DatabricksWorkspace(
id=workspace_id,
name=workspace_name,
location="eastus",
public_network_access=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_public_network_access_disabled.databricks_workspace_public_network_access_disabled.databricks_client",
new=databricks_client,
),
):
from prowler.providers.azure.services.databricks.databricks_workspace_public_network_access_disabled.databricks_workspace_public_network_access_disabled import (
databricks_workspace_public_network_access_disabled,
)
check = databricks_workspace_public_network_access_disabled()
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_DISPLAY} has public network access enabled."
)
def test_databricks_workspace_public_network_access_disabled(self):
workspace_id = str(uuid4())
workspace_name = "test-workspace"
databricks_client = mock.MagicMock
databricks_client.subscriptions = {
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
}
databricks_client.workspaces = {
AZURE_SUBSCRIPTION_ID: {
workspace_id: DatabricksWorkspace(
id=workspace_id,
name=workspace_name,
location="eastus",
public_network_access="Disabled",
)
}
}
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_public_network_access_disabled.databricks_workspace_public_network_access_disabled.databricks_client",
new=databricks_client,
),
):
from prowler.providers.azure.services.databricks.databricks_workspace_public_network_access_disabled.databricks_workspace_public_network_access_disabled import (
databricks_workspace_public_network_access_disabled,
)
check = databricks_workspace_public_network_access_disabled()
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_DISPLAY} has public network access disabled."
)
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"