Checks/Section2.ps1
|
# Section 2 — Azure Databricks # CIS Microsoft Azure Foundations Benchmark v6.0.0 function Invoke-Section2Checks { [CmdletBinding()] param( [string]$SubscriptionId, [string]$SubscriptionName, [hashtable]$PrefetchData ) $results = [System.Collections.Generic.List[object]]::new() $workspaces = @(Get-PrefetchData -PrefetchData $PrefetchData -Key "databricks" -SubscriptionId $SubscriptionId) $sid = $SubscriptionId $sname = $SubscriptionName $wsControlIds = @("2.1.1","2.1.2","2.1.7","2.1.9","2.1.10","2.1.11") # Prefetch failure — emit ERROR (never a false INFO) for all dependent controls $wsErr = Get-PrefetchError -PrefetchData $PrefetchData -Key "databricks" if ($wsErr) { foreach ($cid in $wsControlIds) { $results.Add((New-ErrorResult $cid "Databricks prefetch failed: $wsErr" $sid $sname)) } return $results.ToArray() } if ($workspaces.Count -eq 0) { foreach ($cid in $wsControlIds) { $results.Add((New-InfoResult $cid "No Databricks workspaces found." $sid $sname)) } return $results.ToArray() } foreach ($ws in $workspaces) { $name = [string]$ws.name # 2.1.2 — NSGs configured on Databricks custom VNet subnets (Level 1) $subnets = @(Get-PrefetchData -PrefetchData $PrefetchData -Key "subnets" -SubscriptionId $sid) $vnetId = [string]($ws.PSObject.Properties['vnetId']?.Value) # 2.1.1 — Databricks deployed in a customer-managed VNet (vnetId present = custom VNet) $hasCustomVnet = [bool]$vnetId $results.Add((New-CISResult ` -ControlId "2.1.1" ` -Status $(if ($hasCustomVnet) { $script:PASS } else { $script:FAIL }) ` -Details $(if ($hasCustomVnet) { "Workspace '$name': deployed into customer-managed VNet '$($vnetId.Split('/')[-1])'." } else { "Workspace '$name': uses the Azure-managed VNet (not a customer-managed VNet)." }) ` -Remediation $(if (-not $hasCustomVnet) { "Redeploy the Databricks workspace with VNet injection into a customer-managed virtual network." } else { "" }) ` -SubscriptionId $sid -SubscriptionName $sname -Resource $name)) if (-not $vnetId) { $results.Add((New-InfoResult "2.1.2" "Workspace '$name': no custom VNet (managed VNet in use — Azure manages NSGs)." $sid $sname $name)) } elseif (Get-PrefetchError -PrefetchData $PrefetchData -Key "subnets") { $results.Add((New-ErrorResult "2.1.2" "Subnet prefetch failed: $(Get-PrefetchError -PrefetchData $PrefetchData -Key 'subnets')" $sid $sname $name)) } else { $vnetName = $vnetId.Split('/')[-1] $dbSubnets = @($subnets | Where-Object { [string]$_.vnetName -ieq $vnetName -and [string]$_.subnetName -match '(?i)databricks' }) if ($dbSubnets.Count -eq 0) { $results.Add((New-InfoResult "2.1.2" "Workspace '$name': could not identify Databricks subnets in VNet '$vnetName'." $sid $sname $name)) } else { $missing = @($dbSubnets | Where-Object { [string]$_.hasNsg -notmatch '(?i)^true$' } | ForEach-Object { [string]$_.subnetName }) $pass = $missing.Count -eq 0 $results.Add((New-CISResult ` -ControlId "2.1.2" ` -Status $(if ($pass) { $script:PASS } else { $script:FAIL }) ` -Details $(if ($pass) { "Workspace '$name': all Databricks subnets have NSGs." } else { "Workspace '$name': subnets without NSG: $($missing -join ', ')" }) ` -Remediation $(if (-not $pass) { "Associate NSGs with the public and private Databricks subnets in VNet '$vnetName'." } else { "" }) ` -SubscriptionId $sid -SubscriptionName $sname -Resource $name)) } } # 2.1.7 — Diagnostic logging try { $settings = @(Get-AzDiagnosticSetting -ResourceId $ws.id -ErrorAction Stop) $hasLogs = $settings.Count -gt 0 $results.Add((New-CISResult ` -ControlId "2.1.7" ` -Status $(if ($hasLogs) { $script:PASS } else { $script:FAIL }) ` -Details $(if ($hasLogs) { "Diagnostic settings configured." } else { "No diagnostic settings found on workspace." }) ` -Remediation $(if (-not $hasLogs) { "Azure Portal > Databricks > $name > Diagnostic Settings > Add diagnostic setting" } else { "" }) ` -SubscriptionId $sid -SubscriptionName $sname -Resource $name)) } catch { $results.Add((New-ErrorResult "2.1.7" $_.Exception.Message $sid $sname $name)) } # 2.1.9 — No public IP $noPublicIp = [string]($ws.PSObject.Properties['noPublicIp']?.Value) $noPublicIpEnabled = $noPublicIp -eq "true" -or $noPublicIp -eq "True" $results.Add((New-CISResult ` -ControlId "2.1.9" ` -Status $(if ($noPublicIpEnabled) { $script:PASS } else { $script:FAIL }) ` -Details $(if ($noPublicIpEnabled) { "No Public IP is enabled." } else { "No Public IP is not enabled." }) ` -Remediation $(if (-not $noPublicIpEnabled) { "Portal > Databricks > $name > Networking > Enable No Public IP" } else { "" }) ` -SubscriptionId $sid -SubscriptionName $sname -Resource $name)) # 2.1.10 — Public network access disabled $pubAccess = [string]($ws.PSObject.Properties['publicAccess']?.Value) $pubDisabled = $pubAccess -eq "Disabled" $results.Add((New-CISResult ` -ControlId "2.1.10" ` -Status $(if ($pubDisabled) { $script:PASS } else { $script:FAIL }) ` -Details $(if ($pubDisabled) { "Public network access is disabled." } else { "Public network access is enabled (value: $pubAccess)." }) ` -Remediation $(if (-not $pubDisabled) { "Portal > Databricks > $name > Networking > Disable public network access" } else { "" }) ` -SubscriptionId $sid -SubscriptionName $sname -Resource $name)) # 2.1.11 — Private endpoints $privateEps = [int]($ws.PSObject.Properties['privateEps']?.Value) $hasPrivateEps = $privateEps -gt 0 $results.Add((New-CISResult ` -ControlId "2.1.11" ` -Status $(if ($hasPrivateEps) { $script:PASS } else { $script:FAIL }) ` -Details $(if ($hasPrivateEps) { "$privateEps private endpoint(s) configured." } else { "No private endpoints configured." }) ` -Remediation $(if (-not $hasPrivateEps) { "Portal > Databricks > $name > Networking > Private Endpoint Connections > Add" } else { "" }) ` -SubscriptionId $sid -SubscriptionName $sname -Resource $name)) } return $results.ToArray() } function Invoke-Section2TenantChecks { <# .SYNOPSIS Section 2 (Databricks) controls v6 defines as Manual — surfaced once so the report mirrors the benchmark's control set. #> $manual = @( @{ Id="2.1.3"; Msg="Manual verification required — confirm encryption of traffic between Databricks cluster worker nodes is enabled (Premium tier; cluster configuration)." } @{ Id="2.1.4"; Msg="Manual verification required — confirm SCIM provisioning syncs users and groups from Microsoft Entra ID to the Databricks workspace." } @{ Id="2.1.5"; Msg="Manual verification required — confirm Unity Catalog is configured for centralized governance of the Databricks workspace." } @{ Id="2.1.6"; Msg="Manual verification required — confirm Personal Access Token usage is restricted and token expiry is enforced in the workspace admin settings." } @{ Id="2.1.8"; Msg="Manual verification required — confirm critical Databricks data (DBFS root, managed services) is encrypted with customer-managed keys." } @{ Id="2.1.12"; Msg="Manual verification required — confirm Databricks workspace groups and their memberships are reviewed on a regular basis." } ) $results = [System.Collections.Generic.List[object]]::new() foreach ($m in $manual) { $results.Add((New-ManualResult $m.Id $m.Msg)) } return $results.ToArray() } |