diff --git a/.azuredevops/modulePipelines/ms.databricks.workspaces.yml b/.azuredevops/modulePipelines/ms.databricks.workspaces.yml index c72361cd94..c582be6217 100644 --- a/.azuredevops/modulePipelines/ms.databricks.workspaces.yml +++ b/.azuredevops/modulePipelines/ms.databricks.workspaces.yml @@ -30,6 +30,7 @@ trigger: - '/.azuredevops/modulePipelines/ms.databricks.workspaces.yml' - '/.azuredevops/pipelineTemplates/*.yml' - '/modules/databricks/workspace/*' + - '/modules/network/private-endpoint/*' - '/utilities/pipelines/*' exclude: - '/utilities/pipelines/deploymentRemoval/*' diff --git a/.github/workflows/ms.databricks.workspaces.yml b/.github/workflows/ms.databricks.workspaces.yml index 7b61030788..5ce6c9aef1 100644 --- a/.github/workflows/ms.databricks.workspaces.yml +++ b/.github/workflows/ms.databricks.workspaces.yml @@ -31,6 +31,7 @@ on: - '.github/workflows/template.module.yml' - '.github/workflows/ms.databricks.workspaces.yml' - 'modules/databricks/workspace/**' + - 'modules/network/private-endpoint/**' - 'utilities/pipelines/**' - '!utilities/pipelines/deploymentRemoval/**' - '!*/**/README.md' diff --git a/modules/databricks/workspace/.test/common/dependencies.bicep b/modules/databricks/workspace/.test/common/dependencies.bicep index a7f42aee7b..7030a8aa0a 100644 --- a/modules/databricks/workspace/.test/common/dependencies.bicep +++ b/modules/databricks/workspace/.test/common/dependencies.bicep @@ -4,10 +4,330 @@ param location string = resourceGroup().location @description('Required. The name of the Managed Identity to create.') param managedIdentityName string +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Azure Machine Learning Workspace to create.') +param amlWorkspaceName string + +@description('Required. The name of the Load Balancer to create.') +param loadBalancerName string + +@description('Required. The name of the Network Security Group to create.') +param networkSecurityGroupName string + +@description('Required. The name of the Storage Account to create.') +param storageAccountName string + +@description('Required. The name of the Application Insights Instanec to create.') +param applicationInsightsName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +var addressPrefix = '10.0.0.0/16' + resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { name: managedIdentityName location: location } +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: true // Required by batch account + softDeleteRetentionInDays: 7 + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } + + resource key 'keys@2022-07-01' = { + name: 'keyEncryptionKey' + properties: { + kty: 'RSA' + } + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${keyVault::key.id}-${location}-${managedIdentity.id}-Key-Key-Vault-Crypto-User-RoleAssignment') + scope: keyVault::key + properties: { + principalId: '5167ea7a-355a-466f-ae8b-8ea60f718b35' // AzureDatabricks Enterprise Application Object Id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424') // Key Vault Crypto User + principalType: 'ServicePrincipal' + } +} + +resource amlPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${keyVault.id}-${location}-${managedIdentity.id}-Key-Vault-Contributor') + scope: keyVault + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') // Contributor + principalType: 'ServicePrincipal' + } +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_ZRS' + } + kind: 'StorageV2' + properties: {} +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: applicationInsightsName + location: location + kind: 'web' + properties: { + Application_Type: 'web' + } +} + +resource machineLearningWorkspace 'Microsoft.MachineLearningServices/workspaces@2023-04-01' = { + name: amlWorkspaceName + location: location + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + storageAccount: storageAccount.id + keyVault: keyVault.id + applicationInsights: applicationInsights.id + primaryUserAssignedIdentity: managedIdentity.id + } +} + +resource loadBalancer 'Microsoft.Network/loadBalancers@2023-04-01' = { + name: loadBalancerName + location: location + properties: { + backendAddressPools: [ + { + name: 'default' + } + ] + frontendIPConfigurations: [ + { + name: 'privateIPConfig1' + properties: { + subnet: { + id: virtualNetwork.properties.subnets[0].id + } + } + } + ] + } +} + +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-04-01' = { + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-worker-inbound' + properties: { + description: 'Required for worker nodes communication within a cluster.' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-databricks-webapp' + properties: { + description: 'Required for workers communication with Databricks Webapp.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureDatabricks' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-sql' + properties: { + description: 'Required for workers communication with Azure SQL services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3306' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Sql' + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-storage' + properties: { + description: 'Required for workers communication with Azure Storage services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Storage' + access: 'Allow' + priority: 102 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-worker-outbound' + properties: { + description: 'Required for worker nodes communication within a cluster.' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 103 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-eventhub' + properties: { + description: 'Required for worker communication with Azure Eventhub services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '9093' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'EventHub' + access: 'Allow' + priority: 104 + direction: 'Outbound' + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 20, 0) + } + } + { + name: 'custom-public-subnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 20, 1) + networkSecurityGroup: { + id: networkSecurityGroup.id + } + delegations: [ + { + name: 'databricksDelegation' + properties: { + serviceName: 'Microsoft.Databricks/workspaces' + } + } + ] + } + } + { + name: 'custom-private-subnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 20, 2) + networkSecurityGroup: { + id: networkSecurityGroup.id + } + delegations: [ + { + name: 'databricksDelegation' + properties: { + serviceName: 'Microsoft.Databricks/workspaces' + } + } + ] + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.azuredatabricks.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +@description('The resource ID of the created Virtual Network Default Subnet.') +output defaultSubnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The name of the created Virtual Network Public Subnet.') +output customPublicSubnetName string = virtualNetwork.properties.subnets[1].name + +@description('The name of the created Virtual Network Private Subnet.') +output customPrivateSubnetName string = virtualNetwork.properties.subnets[2].name + +@description('The resource ID of the created Virtual Network.') +output virtualNetworkResourceId string = virtualNetwork.id + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSResourceId string = privateDNSZone.id + +@description('The resource ID of the created Azure Machine Learning Workspace.') +output machineLearningWorkspaceResourceId string = machineLearningWorkspace.id + +@description('The resource ID of the created Key Vault.') +output keyVaultResourceId string = keyVault.id + +@description('The resource ID of the created Load Balancer.') +output loadBalancerResourceId string = loadBalancer.id + +@description('The name of the created Load Balancer Backend Pool.') +output loadBalancerBackendPoolName string = loadBalancer.properties.backendAddressPools[0].name + +@description('The name of the created Key Vault encryption key.') +output keyVaultKeyName string = keyVault::key.name + @description('The principal ID of the created Managed Identity.') output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/modules/databricks/workspace/.test/common/main.test.bicep b/modules/databricks/workspace/.test/common/main.test.bicep index 176ffdea91..9dbc424bf9 100644 --- a/modules/databricks/workspace/.test/common/main.test.bicep +++ b/modules/databricks/workspace/.test/common/main.test.bicep @@ -17,6 +17,9 @@ param serviceShort string = 'dwcom' @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') + @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '[[namePrefix]]' @@ -36,6 +39,14 @@ module nestedDependencies 'dependencies.bicep' = { name: '${uniqueString(deployment().name, location)}-nestedDependencies' params: { managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + amlWorkspaceName: 'dep-${namePrefix}-aml-${serviceShort}' + applicationInsightsName: 'dep-${namePrefix}-appi-${serviceShort}' + loadBalancerName: 'dep-${namePrefix}-lb-${serviceShort}' + storageAccountName: 'dep${namePrefix}sa${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + networkSecurityGroupName: 'dep-${namePrefix}-nsg-${serviceShort}' + // Adding base time to make the name unique as purge protection must be enabled (but may not be longer than 24 characters total) + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}-${substring(uniqueString(baseTime), 0, 3)}' } } @@ -45,7 +56,7 @@ module diagnosticDependencies '../../../../.shared/.templates/diagnostic.depende scope: resourceGroup name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' params: { - storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + storageAccountName: 'dep${namePrefix}diasa${serviceShort}' logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' @@ -82,5 +93,49 @@ module testDeployment '../../main.bicep' = { Environment: 'Non-Prod' Role: 'DeploymentValidation' } + cMKManagedServicesKeyName: nestedDependencies.outputs.keyVaultKeyName + cMKManagedServicesKeyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId + cMKManagedDisksKeyName: nestedDependencies.outputs.keyVaultKeyName + cMKManagedDisksKeyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId + cMKManagedDisksKeyRotationToLatestKeyVersionEnabled: true + storageAccountName: 'sa${namePrefix}${serviceShort}001' + storageAccountSkuName: 'Standard_ZRS' + publicIpName: 'nat-gw-public-ip' + natGatewayName: 'nat-gateway' + prepareEncryption: true + requiredNsgRules: 'NoAzureDatabricksRules' + skuName: 'premium' + amlWorkspaceResourceId: nestedDependencies.outputs.machineLearningWorkspaceResourceId + customPrivateSubnetName: nestedDependencies.outputs.customPrivateSubnetName + customPublicSubnetName: nestedDependencies.outputs.customPublicSubnetName + publicNetworkAccess: 'Disabled' + disablePublicIp: true + loadBalancerResourceId: nestedDependencies.outputs.loadBalancerResourceId + loadBalancerBackendPoolName: nestedDependencies.outputs.loadBalancerBackendPoolName + customVirtualNetworkResourceId: nestedDependencies.outputs.virtualNetworkResourceId + privateEndpoints: [ + { + privateDnsZoneGroup: { + privateDNSResourceIds: [ + nestedDependencies.outputs.privateDNSResourceId + ] + } + service: 'databricks_ui_api' + subnetResourceId: nestedDependencies.outputs.defaultSubnetResourceId + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + managedResourceGroupResourceId: '${subscription().id}/resourceGroups/rg-${resourceGroupName}-managed' + diagnosticLogCategoriesToEnable: [ + 'jobs' + 'notebook' + ] + diagnosticSettingsName: 'diag${namePrefix}${serviceShort}001' + requireInfrastructureEncryption: true + vnetAddressPrefix: '10.100' + location: resourceGroup.location } } diff --git a/modules/databricks/workspace/.test/main.test.bicep b/modules/databricks/workspace/.test/main.test.bicep index dff85d99b8..ef99b3dc03 100644 --- a/modules/databricks/workspace/.test/main.test.bicep +++ b/modules/databricks/workspace/.test/main.test.bicep @@ -13,3 +13,11 @@ module common 'common/main.test.bicep' = { namePrefix: namePrefix } } + +// TEST 2 - MIN +module min 'min/main.test.bicep' = { + name: '${uniqueString(deployment().name)}-min-test' + params: { + namePrefix: namePrefix + } +} diff --git a/modules/databricks/workspace/.test/min/main.test.bicep b/modules/databricks/workspace/.test/min/main.test.bicep new file mode 100644 index 0000000000..85cd2ef8dc --- /dev/null +++ b/modules/databricks/workspace/.test/min/main.test.bicep @@ -0,0 +1,45 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.databricks.workspaces-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'dwmin' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '[[namePrefix]]' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '${namePrefix}${serviceShort}001' + } +} diff --git a/modules/databricks/workspace/README.md b/modules/databricks/workspace/README.md index 51574a9991..5f1ba4b232 100644 --- a/modules/databricks/workspace/README.md +++ b/modules/databricks/workspace/README.md @@ -16,8 +16,10 @@ This module deploys an Azure Databricks Workspace. | :-- | :-- | | `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Databricks/workspaces` | [2018-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Databricks/2018-04-01/workspaces) | +| `Microsoft.Databricks/workspaces` | [2023-02-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Databricks/2023-02-01/workspaces) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | ## Parameters @@ -27,24 +29,52 @@ This module deploys an Azure Databricks Workspace. | :-- | :-- | :-- | | `name` | string | The name of the Azure Databricks workspace to create. | +**Conditional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `cMKManagedDisksKeyVaultResourceId` | string | `''` | The resource ID of a key vault to reference a customer managed key for encryption from. Required if 'cMKKeyName' is not empty. | +| `cMKManagedServicesKeyVaultResourceId` | string | `''` | The resource ID of a key vault to reference a customer managed key for encryption from. Required if 'cMKKeyName' is not empty. | + **Optional parameters** | Parameter Name | Type | Default Value | Allowed Values | Description | | :-- | :-- | :-- | :-- | :-- | +| `amlWorkspaceResourceId` | string | `''` | | The resource ID of a Azure Machine Learning workspace to link with Databricks workspace. | +| `cMKManagedDisksKeyName` | string | `''` | | The name of the customer managed key to use for encryption. | +| `cMKManagedDisksKeyRotationToLatestKeyVersionEnabled` | bool | `True` | | Enable Auto Rotation of Key. | +| `cMKManagedDisksKeyVersion` | string | `''` | | The version of the customer managed key to reference for encryption. If not provided, the latest key version is used. | +| `cMKManagedServicesKeyName` | string | `''` | | The name of the customer managed key to use for encryption. | +| `cMKManagedServicesKeyVersion` | string | `''` | | The version of the customer managed key to reference for encryption. If not provided, the latest key version is used. | +| `customPrivateSubnetName` | string | `''` | | The name of the Private Subnet within the Virtual Network. | +| `customPublicSubnetName` | string | `''` | | The name of a Public Subnet within the Virtual Network. | +| `customVirtualNetworkResourceId` | string | `''` | | The resource ID of a Virtual Network where this Databricks Cluster should be created. | | `diagnosticEventHubAuthorizationRuleId` | string | `''` | | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | | `diagnosticEventHubName` | string | `''` | | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. | | `diagnosticLogCategoriesToEnable` | array | `[allLogs]` | `['', accounts, allLogs, clusters, dbfs, instancePools, jobs, notebook, secrets, sqlPermissions, ssh, workspace]` | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to '' to disable log collection. | | `diagnosticSettingsName` | string | `''` | | The name of the diagnostic setting, if deployed. If left empty, it defaults to "-diagnosticSettings". | | `diagnosticStorageAccountId` | string | `''` | | Resource ID of the diagnostic storage account. | | `diagnosticWorkspaceId` | string | `''` | | Resource ID of the diagnostic log analytics workspace. | +| `disablePublicIp` | bool | `False` | | Disable Public IP. | | `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `loadBalancerBackendPoolName` | string | `''` | | Name of the outbound Load Balancer Backend Pool for Secure Cluster Connectivity (No Public IP). | +| `loadBalancerResourceId` | string | `''` | | Resource URI of Outbound Load balancer for Secure Cluster Connectivity (No Public IP) workspace. | | `location` | string | `[resourceGroup().location]` | | Location for all Resources. | | `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | -| `managedResourceGroupId` | string | `''` | | The managed resource group ID. | -| `parameters` | object | `{object}` | | The workspace's custom parameters. | -| `pricingTier` | string | `'premium'` | `[premium, standard, trial]` | The pricing tier of workspace. | +| `managedResourceGroupResourceId` | string | `''` | | The managed resource group ID. It is created by the module as per the to-be resource ID you provide. | +| `natGatewayName` | string | `''` | | Name of the NAT gateway for Secure Cluster Connectivity (No Public IP) workspace subnets. | +| `prepareEncryption` | bool | `False` | | Prepare the workspace for encryption. Enables the Managed Identity for managed storage account. | +| `privateEndpoints` | array | `[]` | | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. | +| `publicIpName` | string | `''` | | Name of the Public IP for No Public IP workspace with managed vNet. | +| `publicNetworkAccess` | string | `'Enabled'` | `[Disabled, Enabled]` | The network access type for accessing workspace. Set value to disabled to access workspace only via private link. | +| `requiredNsgRules` | string | `'AllRules'` | `[AllRules, NoAzureDatabricksRules]` | Gets or sets a value indicating whether data plane (clusters) to control plane communication happen over private endpoint. | +| `requireInfrastructureEncryption` | bool | `False` | | A boolean indicating whether or not the DBFS root file system will be enabled with secondary layer of encryption with platform managed keys for data at rest. | | `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| `skuName` | string | `'premium'` | `[premium, standard, trial]` | The pricing tier of workspace. | +| `storageAccountName` | string | `''` | | Default DBFS storage account name. | +| `storageAccountSkuName` | string | `'Standard_GRS'` | | Storage account SKU name. | | `tags` | object | `{object}` | | Tags of the resource. | +| `vnetAddressPrefix` | string | `'10.139'` | | Address prefix for Managed virtual network. | ### Parameter Usage: `roleAssignments` @@ -212,6 +242,106 @@ tags: {

+### Parameter Usage: `privateEndpoints` + +To use Private Endpoint the following dependencies must be deployed: + +- Destination subnet must be created with the following configuration option - `"privateEndpointNetworkPolicies": "Disabled"`. Setting this option acknowledges that NSG rules are not applied to Private Endpoints (this capability is coming soon). A full example is available in the Virtual Network Module. +- Although not strictly required, it is highly recommended to first create a private DNS Zone to host Private Endpoint DNS records. See [Azure Private Endpoint DNS configuration](https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-dns) for more information. + +

+ +Parameter JSON format + +```json +"privateEndpoints": { + "value": [ + // Example showing all available fields + { + "name": "sxx-az-pe", // Optional: Name will be automatically generated if one is not provided here + "subnetResourceId": "/subscriptions/[[subscriptionId]]/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001", + "service": "", // e.g. vault, registry, blob + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ // Optional: No DNS record will be created if a private DNS zone Resource ID is not specified + "/subscriptions/[[subscriptionId]]/resourceGroups/validation-rg/providers/Microsoft.Network/privateDnsZones/" // e.g. privatelink.vaultcore.azure.net, privatelink.azurecr.io, privatelink.blob.core.windows.net + ] + }, + "ipConfigurations":[ + { + "name": "myIPconfigTest02", + "properties": { + "groupId": "blob", + "memberName": "blob", + "privateIPAddress": "10.0.0.30" + } + } + ], + "customDnsConfigs": [ + { + "fqdn": "customname.test.local", + "ipAddresses": [ + "10.10.10.10" + ] + } + ] + }, + // Example showing only mandatory fields + { + "subnetResourceId": "/subscriptions/[[subscriptionId]]/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001", + "service": "" // e.g. vault, registry, blob + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +privateEndpoints: [ + // Example showing all available fields + { + name: 'sxx-az-pe' // Optional: Name will be automatically generated if one is not provided here + subnetResourceId: '/subscriptions/[[subscriptionId]]/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001' + service: '' // e.g. vault, registry, blob + privateDnsZoneGroup: { + privateDNSResourceIds: [ // Optional: No DNS record will be created if a private DNS zone Resource ID is not specified + '/subscriptions/[[subscriptionId]]/resourceGroups/validation-rg/providers/Microsoft.Network/privateDnsZones/' // e.g. privatelink.vaultcore.azure.net, privatelink.azurecr.io, privatelink.blob.core.windows.net + ] + } + customDnsConfigs: [ + { + fqdn: 'customname.test.local' + ipAddresses: [ + '10.10.10.10' + ] + } + ] + ipConfigurations:[ + { + name: 'myIPconfigTest02' + properties: { + groupId: 'blob' + memberName: 'blob' + privateIPAddress: '10.0.0.30' + } + } + ] + } + // Example showing only mandatory fields + { + subnetResourceId: '/subscriptions/[[subscriptionId]]/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001' + service: '' // e.g. vault, registry, blob + } +] +``` + +
+

+ ## Outputs | Output Name | Type | Description | @@ -223,7 +353,11 @@ tags: { ## Cross-referenced modules -_None_ +This section gives you an overview of all local-referenced module files (i.e., other CARML modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `network/private-endpoint` | Local reference | ## Deployment examples @@ -245,12 +379,52 @@ module workspace './databricks/workspace/main.bicep' = { // Required parameters name: 'dwcom001' // Non-required parameters + amlWorkspaceResourceId: '' + cMKManagedDisksKeyName: '' + cMKManagedDisksKeyRotationToLatestKeyVersionEnabled: true + cMKManagedDisksKeyVaultResourceId: '' + cMKManagedServicesKeyName: '' + cMKManagedServicesKeyVaultResourceId: '' + customPrivateSubnetName: '' + customPublicSubnetName: '' + customVirtualNetworkResourceId: '' diagnosticEventHubAuthorizationRuleId: '' diagnosticEventHubName: '' + diagnosticLogCategoriesToEnable: [ + 'jobs' + 'notebook' + ] + diagnosticSettingsName: 'diagdwcom001' diagnosticStorageAccountId: '' diagnosticWorkspaceId: '' + disablePublicIp: true enableDefaultTelemetry: '' + loadBalancerBackendPoolName: '' + loadBalancerResourceId: '' + location: '' lock: 'CanNotDelete' + managedResourceGroupResourceId: '' + natGatewayName: 'nat-gateway' + prepareEncryption: true + privateEndpoints: [ + { + privateDnsZoneGroup: { + privateDNSResourceIds: [ + '' + ] + } + service: 'databricks_ui_api' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + publicIpName: 'nat-gw-public-ip' + publicNetworkAccess: 'Disabled' + requiredNsgRules: 'NoAzureDatabricksRules' + requireInfrastructureEncryption: true roleAssignments: [ { principalIds: [ @@ -260,11 +434,15 @@ module workspace './databricks/workspace/main.bicep' = { roleDefinitionIdOrName: 'Reader' } ] + skuName: 'premium' + storageAccountName: 'sadwcom001' + storageAccountSkuName: 'Standard_ZRS' tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' Role: 'DeploymentValidation' } + vnetAddressPrefix: '10.100' } } ``` @@ -286,24 +464,110 @@ module workspace './databricks/workspace/main.bicep' = { "value": "dwcom001" }, // Non-required parameters + "amlWorkspaceResourceId": { + "value": "" + }, + "cMKManagedDisksKeyName": { + "value": "" + }, + "cMKManagedDisksKeyRotationToLatestKeyVersionEnabled": { + "value": true + }, + "cMKManagedDisksKeyVaultResourceId": { + "value": "" + }, + "cMKManagedServicesKeyName": { + "value": "" + }, + "cMKManagedServicesKeyVaultResourceId": { + "value": "" + }, + "customPrivateSubnetName": { + "value": "" + }, + "customPublicSubnetName": { + "value": "" + }, + "customVirtualNetworkResourceId": { + "value": "" + }, "diagnosticEventHubAuthorizationRuleId": { "value": "" }, "diagnosticEventHubName": { "value": "" }, + "diagnosticLogCategoriesToEnable": { + "value": [ + "jobs", + "notebook" + ] + }, + "diagnosticSettingsName": { + "value": "diagdwcom001" + }, "diagnosticStorageAccountId": { "value": "" }, "diagnosticWorkspaceId": { "value": "" }, + "disablePublicIp": { + "value": true + }, "enableDefaultTelemetry": { "value": "" }, + "loadBalancerBackendPoolName": { + "value": "" + }, + "loadBalancerResourceId": { + "value": "" + }, + "location": { + "value": "" + }, "lock": { "value": "CanNotDelete" }, + "managedResourceGroupResourceId": { + "value": "" + }, + "natGatewayName": { + "value": "nat-gateway" + }, + "prepareEncryption": { + "value": true + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ + "" + ] + }, + "service": "databricks_ui_api", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + ] + }, + "publicIpName": { + "value": "nat-gw-public-ip" + }, + "publicNetworkAccess": { + "value": "Disabled" + }, + "requiredNsgRules": { + "value": "NoAzureDatabricksRules" + }, + "requireInfrastructureEncryption": { + "value": true + }, "roleAssignments": { "value": [ { @@ -315,12 +579,69 @@ module workspace './databricks/workspace/main.bicep' = { } ] }, + "skuName": { + "value": "premium" + }, + "storageAccountName": { + "value": "sadwcom001" + }, + "storageAccountSkuName": { + "value": "Standard_ZRS" + }, "tags": { "value": { "Environment": "Non-Prod", "hidden-title": "This is visible in the resource name", "Role": "DeploymentValidation" } + }, + "vnetAddressPrefix": { + "value": "10.100" + } + } +} +``` + + +

+ +

Example 2: Min

+ +
+ +via Bicep module + +```bicep +module workspace './databricks/workspace/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-dwmin' + params: { + // Required parameters + name: 'dwmin001' + // Non-required parameters + enableDefaultTelemetry: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "dwmin001" + }, + // Non-required parameters + "enableDefaultTelemetry": { + "value": "" } } } diff --git a/modules/databricks/workspace/main.bicep b/modules/databricks/workspace/main.bicep index 585ff24171..e88461ce0a 100644 --- a/modules/databricks/workspace/main.bicep +++ b/modules/databricks/workspace/main.bicep @@ -5,8 +5,8 @@ metadata owner = 'Azure/module-maintainers' @description('Required. The name of the Azure Databricks workspace to create.') param name string -@description('Optional. The managed resource group ID.') -param managedResourceGroupId string = '' +@description('Optional. The managed resource group ID. It is created by the module as per the to-be resource ID you provide.') +param managedResourceGroupResourceId string = '' @description('Optional. The pricing tier of workspace.') @allowed([ @@ -14,7 +14,7 @@ param managedResourceGroupId string = '' 'standard' 'premium' ]) -param pricingTier string = 'premium' +param skuName string = 'premium' @description('Optional. Location for all Resources.') param location string = resourceGroup().location @@ -22,9 +22,6 @@ param location string = resourceGroup().location @description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') param roleAssignments array = [] -@description('Optional. The workspace\'s custom parameters.') -param parameters object = {} - @description('Optional. Resource ID of the diagnostic storage account.') param diagnosticStorageAccountId string = '' @@ -51,6 +48,86 @@ param tags object = {} @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true +@description('Optional. The resource ID of a Virtual Network where this Databricks Cluster should be created.') +param customVirtualNetworkResourceId string = '' + +@description('Optional. The resource ID of a Azure Machine Learning workspace to link with Databricks workspace.') +param amlWorkspaceResourceId string = '' + +@description('Optional. The name of the Private Subnet within the Virtual Network.') +param customPrivateSubnetName string = '' + +@description('Optional. The name of a Public Subnet within the Virtual Network.') +param customPublicSubnetName string = '' + +@description('Optional. Disable Public IP.') +param disablePublicIp bool = false + +@description('Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if \'cMKKeyName\' is not empty.') +param cMKManagedServicesKeyVaultResourceId string = '' + +@description('Optional. The name of the customer managed key to use for encryption.') +param cMKManagedServicesKeyName string = '' + +@description('Optional. The version of the customer managed key to reference for encryption. If not provided, the latest key version is used.') +param cMKManagedServicesKeyVersion string = '' + +@description('Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if \'cMKKeyName\' is not empty.') +param cMKManagedDisksKeyVaultResourceId string = '' + +@description('Optional. The name of the customer managed key to use for encryption.') +param cMKManagedDisksKeyName string = '' + +@description('Optional. The version of the customer managed key to reference for encryption. If not provided, the latest key version is used.') +param cMKManagedDisksKeyVersion string = '' + +@description('Optional. Enable Auto Rotation of Key.') +param cMKManagedDisksKeyRotationToLatestKeyVersionEnabled bool = true + +@description('Optional. Name of the outbound Load Balancer Backend Pool for Secure Cluster Connectivity (No Public IP).') +param loadBalancerBackendPoolName string = '' + +@description('Optional. Resource URI of Outbound Load balancer for Secure Cluster Connectivity (No Public IP) workspace.') +param loadBalancerResourceId string = '' + +@description('Optional. Name of the NAT gateway for Secure Cluster Connectivity (No Public IP) workspace subnets.') +param natGatewayName string = '' + +@description('Optional. Prepare the workspace for encryption. Enables the Managed Identity for managed storage account.') +param prepareEncryption bool = false + +@description('Optional. Name of the Public IP for No Public IP workspace with managed vNet.') +param publicIpName string = '' + +@description('Optional. A boolean indicating whether or not the DBFS root file system will be enabled with secondary layer of encryption with platform managed keys for data at rest.') +param requireInfrastructureEncryption bool = false + +@description('Optional. Default DBFS storage account name.') +param storageAccountName string = '' + +@description('Optional. Storage account SKU name.') +param storageAccountSkuName string = 'Standard_GRS' + +@description('Optional. Address prefix for Managed virtual network.') +param vnetAddressPrefix string = '10.139' + +@description('Optional. The network access type for accessing workspace. Set value to disabled to access workspace only via private link.') +@allowed([ + 'Disabled' + 'Enabled' +]) +param publicNetworkAccess string = 'Enabled' + +@description('Optional. Gets or sets a value indicating whether data plane (clusters) to control plane communication happen over private endpoint.') +@allowed([ + 'AllRules' + 'NoAzureDatabricksRules' +]) +param requiredNsgRules string = 'AllRules' + +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints array = [] + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') @allowed([ '' @@ -85,8 +162,7 @@ var diagnosticsLogs = contains(diagnosticLogCategoriesToEnable, 'allLogs') ? [ } ] : contains(diagnosticLogCategoriesToEnable, '') ? [] : diagnosticsLogsSpecified -var managedResourceGroupName = '${name}-rg' -var managedResourceGroupIdVar = '${subscription().id}/resourceGroups/${managedResourceGroupName}' +var enableReferencedModulesTelemetry = false resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' @@ -100,16 +176,125 @@ resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (ena } } -resource workspace 'Microsoft.Databricks/workspaces@2018-04-01' = { +resource cMKManagedDisksKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(cMKManagedDisksKeyVaultResourceId)) { + name: last(split(cMKManagedDisksKeyVaultResourceId, '/'))! + scope: resourceGroup(split(cMKManagedDisksKeyVaultResourceId, '/')[2], split(cMKManagedDisksKeyVaultResourceId, '/')[4]) +} + +resource cMKManagedDisksKeyVaultKey 'Microsoft.KeyVault/vaults/keys@2023-02-01' existing = if (!empty(cMKManagedDisksKeyVaultResourceId) && !empty(cMKManagedDisksKeyName)) { + name: '${last(split(cMKManagedDisksKeyVaultResourceId, '/'))}/${cMKManagedDisksKeyName}'! + scope: resourceGroup(split(cMKManagedDisksKeyVaultResourceId, '/')[2], split(cMKManagedDisksKeyVaultResourceId, '/')[4]) +} + +resource cMKManagedServicesKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(cMKManagedServicesKeyVaultResourceId)) { + name: last(split(cMKManagedServicesKeyVaultResourceId, '/'))! + scope: resourceGroup(split(cMKManagedServicesKeyVaultResourceId, '/')[2], split(cMKManagedServicesKeyVaultResourceId, '/')[4]) +} + +resource cMKManagedServicesKeyVaultKey 'Microsoft.KeyVault/vaults/keys@2023-02-01' existing = if (!empty(cMKManagedServicesKeyVaultResourceId) && !empty(cMKManagedServicesKeyName)) { + name: '${last(split(cMKManagedServicesKeyVaultResourceId, '/'))}/${cMKManagedServicesKeyName}'! + scope: resourceGroup(split(cMKManagedServicesKeyVaultResourceId, '/')[2], split(cMKManagedServicesKeyVaultResourceId, '/')[4]) +} + +resource workspace 'Microsoft.Databricks/workspaces@2023-02-01' = { name: name location: location tags: tags sku: { - name: pricingTier + name: skuName } properties: { - managedResourceGroupId: (empty(managedResourceGroupId) ? managedResourceGroupIdVar : managedResourceGroupId) - parameters: parameters + managedResourceGroupId: !empty(managedResourceGroupResourceId) ? managedResourceGroupResourceId : '${subscription().id}/resourceGroups/${name}-rg' + parameters: union( + // Always added parameters + { + enableNoPublicIp: { + value: disablePublicIp + } + prepareEncryption: { + value: prepareEncryption + } + vnetAddressPrefix: { + value: vnetAddressPrefix + } + requireInfrastructureEncryption: { + value: requireInfrastructureEncryption + } + }, + // Parameters only added if not empty + !empty(customVirtualNetworkResourceId) ? { + customVirtualNetworkId: { + value: customVirtualNetworkResourceId + } + } : {}, + !empty(amlWorkspaceResourceId) ? { + amlWorkspaceId: { + value: amlWorkspaceResourceId + } + } : {}, + !empty(customPrivateSubnetName) ? { + customPrivateSubnetName: { + value: customPrivateSubnetName + } + } : {}, + !empty(customPublicSubnetName) ? { + customPublicSubnetName: { + value: customPublicSubnetName + } + } : {}, + !empty(loadBalancerBackendPoolName) ? { + loadBalancerBackendPoolName: { + value: loadBalancerBackendPoolName + } + } : {}, + !empty(loadBalancerResourceId) ? { + loadBalancerId: { + value: loadBalancerResourceId + } + } : {}, + !empty(natGatewayName) ? { + natGatewayName: { + value: natGatewayName + } + } : {}, + !empty(publicIpName) ? { + publicIpName: { + value: publicIpName + } + } : {}, + !empty(storageAccountName) ? { + storageAccountName: { + value: storageAccountName + } + } : {}, + !empty(storageAccountSkuName) ? { + storageAccountSkuName: { + value: storageAccountSkuName + } + } : {}) + publicNetworkAccess: publicNetworkAccess + requiredNsgRules: requiredNsgRules + encryption: !empty(cMKManagedServicesKeyName) || !empty(cMKManagedServicesKeyName) ? { + entities: { + managedServices: !empty(cMKManagedServicesKeyName) ? { + keySource: 'Microsoft.Keyvault' + keyVaultProperties: { + keyVaultUri: cMKManagedServicesKeyVault.properties.vaultUri + keyName: cMKManagedServicesKeyName + keyVersion: !empty(cMKManagedServicesKeyVersion) ? cMKManagedServicesKeyVersion : last(split(cMKManagedServicesKeyVaultKey.properties.keyUriWithVersion, '/')) + } + } : null + managedDisk: !empty(cMKManagedDisksKeyName) ? { + keySource: 'Microsoft.Keyvault' + keyVaultProperties: { + keyVaultUri: cMKManagedDisksKeyVault.properties.vaultUri + keyName: cMKManagedDisksKeyName + keyVersion: !empty(cMKManagedDisksKeyVersion) ? cMKManagedDisksKeyVersion : last(split(cMKManagedDisksKeyVaultKey.properties.keyUriWithVersion, '/')) + } + rotationToLatestKeyVersionEnabled: cMKManagedDisksKeyRotationToLatestKeyVersionEnabled + } : null + } + } : null } } @@ -123,7 +308,7 @@ resource workspace_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty( } // Note: Diagnostic Settings are only supported by the premium tier -resource workspace_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@2021-05-01-preview' = if (pricingTier == 'premium' && ((!empty(diagnosticStorageAccountId)) || (!empty(diagnosticWorkspaceId)) || (!empty(diagnosticEventHubAuthorizationRuleId)) || (!empty(diagnosticEventHubName)))) { +resource workspace_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@2021-05-01-preview' = if (skuName == 'premium' && ((!empty(diagnosticStorageAccountId)) || (!empty(diagnosticWorkspaceId)) || (!empty(diagnosticEventHubAuthorizationRuleId)) || (!empty(diagnosticEventHubName)))) { name: !empty(diagnosticSettingsName) ? diagnosticSettingsName : '${name}-diagnosticSettings' properties: { storageAccountId: !empty(diagnosticStorageAccountId) ? diagnosticStorageAccountId : null @@ -136,7 +321,7 @@ resource workspace_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@202 } module workspace_roleAssignments '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in roleAssignments: { - name: '${uniqueString(deployment().name, location)}-DataBricks-Rbac-${index}' + name: '${uniqueString(deployment().name, location)}-Databricks-Rbac-${index}' params: { description: contains(roleAssignment, 'description') ? roleAssignment.description : '' principalIds: roleAssignment.principalIds @@ -148,6 +333,29 @@ module workspace_roleAssignments '.bicep/nested_roleAssignments.bicep' = [for (r } }] +module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { + name: '${uniqueString(deployment().name, location)}-Databricks-PrivateEndpoint-${index}' + params: { + groupIds: [ + privateEndpoint.service + ] + name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(workspace.id, '/'))}-${privateEndpoint.service}-${index}' + serviceResourceId: workspace.id + subnetResourceId: privateEndpoint.subnetResourceId + enableDefaultTelemetry: enableReferencedModulesTelemetry + location: reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: contains(privateEndpoint, 'lock') ? privateEndpoint.lock : lock + privateDnsZoneGroup: contains(privateEndpoint, 'privateDnsZoneGroup') ? privateEndpoint.privateDnsZoneGroup : {} + roleAssignments: contains(privateEndpoint, 'roleAssignments') ? privateEndpoint.roleAssignments : [] + tags: contains(privateEndpoint, 'tags') ? privateEndpoint.tags : {} + manualPrivateLinkServiceConnections: contains(privateEndpoint, 'manualPrivateLinkServiceConnections') ? privateEndpoint.manualPrivateLinkServiceConnections : [] + customDnsConfigs: contains(privateEndpoint, 'customDnsConfigs') ? privateEndpoint.customDnsConfigs : [] + ipConfigurations: contains(privateEndpoint, 'ipConfigurations') ? privateEndpoint.ipConfigurations : [] + applicationSecurityGroups: contains(privateEndpoint, 'applicationSecurityGroups') ? privateEndpoint.applicationSecurityGroups : [] + customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + } +}] + @description('The name of the deployed databricks workspace.') output name string = workspace.name diff --git a/modules/databricks/workspace/main.json b/modules/databricks/workspace/main.json index 2c5e42b8c7..4437110d43 100644 --- a/modules/databricks/workspace/main.json +++ b/modules/databricks/workspace/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.20.4.51522", - "templateHash": "17661847744091981452" + "templateHash": "14136317346083571214" }, "name": "Azure Databricks Workspaces", "description": "This module deploys an Azure Databricks Workspace.", @@ -18,14 +18,14 @@ "description": "Required. The name of the Azure Databricks workspace to create." } }, - "managedResourceGroupId": { + "managedResourceGroupResourceId": { "type": "string", "defaultValue": "", "metadata": { - "description": "Optional. The managed resource group ID." + "description": "Optional. The managed resource group ID. It is created by the module as per the to-be resource ID you provide." } }, - "pricingTier": { + "skuName": { "type": "string", "defaultValue": "premium", "allowedValues": [ @@ -51,13 +51,6 @@ "description": "Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." } }, - "parameters": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. The workspace's custom parameters." - } - }, "diagnosticStorageAccountId": { "type": "string", "defaultValue": "", @@ -112,6 +105,182 @@ "description": "Optional. Enable telemetry via a Globally Unique Identifier (GUID)." } }, + "customVirtualNetworkResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of a Virtual Network where this Databricks Cluster should be created." + } + }, + "amlWorkspaceResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of a Azure Machine Learning workspace to link with Databricks workspace." + } + }, + "customPrivateSubnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the Private Subnet within the Virtual Network." + } + }, + "customPublicSubnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of a Public Subnet within the Virtual Network." + } + }, + "disablePublicIp": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Disable Public IP." + } + }, + "cMKManagedServicesKeyVaultResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if 'cMKKeyName' is not empty." + } + }, + "cMKManagedServicesKeyName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the customer managed key to use for encryption." + } + }, + "cMKManagedServicesKeyVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, the latest key version is used." + } + }, + "cMKManagedDisksKeyVaultResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if 'cMKKeyName' is not empty." + } + }, + "cMKManagedDisksKeyName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the customer managed key to use for encryption." + } + }, + "cMKManagedDisksKeyVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, the latest key version is used." + } + }, + "cMKManagedDisksKeyRotationToLatestKeyVersionEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable Auto Rotation of Key." + } + }, + "loadBalancerBackendPoolName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the outbound Load Balancer Backend Pool for Secure Cluster Connectivity (No Public IP)." + } + }, + "loadBalancerResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource URI of Outbound Load balancer for Secure Cluster Connectivity (No Public IP) workspace." + } + }, + "natGatewayName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the NAT gateway for Secure Cluster Connectivity (No Public IP) workspace subnets." + } + }, + "prepareEncryption": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Prepare the workspace for encryption. Enables the Managed Identity for managed storage account." + } + }, + "publicIpName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the Public IP for No Public IP workspace with managed vNet." + } + }, + "requireInfrastructureEncryption": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. A boolean indicating whether or not the DBFS root file system will be enabled with secondary layer of encryption with platform managed keys for data at rest." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Default DBFS storage account name." + } + }, + "storageAccountSkuName": { + "type": "string", + "defaultValue": "Standard_GRS", + "metadata": { + "description": "Optional. Storage account SKU name." + } + }, + "vnetAddressPrefix": { + "type": "string", + "defaultValue": "10.139", + "metadata": { + "description": "Optional. Address prefix for Managed virtual network." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. \tThe network access type for accessing workspace. Set value to disabled to access workspace only via private link." + } + }, + "requiredNsgRules": { + "type": "string", + "defaultValue": "AllRules", + "allowedValues": [ + "AllRules", + "NoAzureDatabricksRules" + ], + "metadata": { + "description": "Optional. Gets or sets a value indicating whether data plane (clusters) to control plane communication happen over private endpoint." + } + }, + "privateEndpoints": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, "diagnosticLogCategoriesToEnable": { "type": "array", "defaultValue": [ @@ -155,8 +324,7 @@ } ], "diagnosticsLogs": "[if(contains(parameters('diagnosticLogCategoriesToEnable'), 'allLogs'), createArray(createObject('categoryGroup', 'allLogs', 'enabled', true())), if(contains(parameters('diagnosticLogCategoriesToEnable'), ''), createArray(), variables('diagnosticsLogsSpecified')))]", - "managedResourceGroupName": "[format('{0}-rg', parameters('name'))]", - "managedResourceGroupIdVar": "[format('{0}/resourceGroups/{1}', subscription().id, variables('managedResourceGroupName'))]" + "enableReferencedModulesTelemetry": false }, "resources": [ { @@ -175,16 +343,19 @@ }, { "type": "Microsoft.Databricks/workspaces", - "apiVersion": "2018-04-01", + "apiVersion": "2023-02-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", "sku": { - "name": "[parameters('pricingTier')]" + "name": "[parameters('skuName')]" }, "properties": { - "managedResourceGroupId": "[if(empty(parameters('managedResourceGroupId')), variables('managedResourceGroupIdVar'), parameters('managedResourceGroupId'))]", - "parameters": "[parameters('parameters')]" + "managedResourceGroupId": "[if(not(empty(parameters('managedResourceGroupResourceId'))), parameters('managedResourceGroupResourceId'), format('{0}/resourceGroups/{1}-rg', subscription().id, parameters('name')))]", + "parameters": "[union(createObject('enableNoPublicIp', createObject('value', parameters('disablePublicIp')), 'prepareEncryption', createObject('value', parameters('prepareEncryption')), 'vnetAddressPrefix', createObject('value', parameters('vnetAddressPrefix')), 'requireInfrastructureEncryption', createObject('value', parameters('requireInfrastructureEncryption'))), if(not(empty(parameters('customVirtualNetworkResourceId'))), createObject('customVirtualNetworkId', createObject('value', parameters('customVirtualNetworkResourceId'))), createObject()), if(not(empty(parameters('amlWorkspaceResourceId'))), createObject('amlWorkspaceId', createObject('value', parameters('amlWorkspaceResourceId'))), createObject()), if(not(empty(parameters('customPrivateSubnetName'))), createObject('customPrivateSubnetName', createObject('value', parameters('customPrivateSubnetName'))), createObject()), if(not(empty(parameters('customPublicSubnetName'))), createObject('customPublicSubnetName', createObject('value', parameters('customPublicSubnetName'))), createObject()), if(not(empty(parameters('loadBalancerBackendPoolName'))), createObject('loadBalancerBackendPoolName', createObject('value', parameters('loadBalancerBackendPoolName'))), createObject()), if(not(empty(parameters('loadBalancerResourceId'))), createObject('loadBalancerId', createObject('value', parameters('loadBalancerResourceId'))), createObject()), if(not(empty(parameters('natGatewayName'))), createObject('natGatewayName', createObject('value', parameters('natGatewayName'))), createObject()), if(not(empty(parameters('publicIpName'))), createObject('publicIpName', createObject('value', parameters('publicIpName'))), createObject()), if(not(empty(parameters('storageAccountName'))), createObject('storageAccountName', createObject('value', parameters('storageAccountName'))), createObject()), if(not(empty(parameters('storageAccountSkuName'))), createObject('storageAccountSkuName', createObject('value', parameters('storageAccountSkuName'))), createObject()))]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "requiredNsgRules": "[parameters('requiredNsgRules')]", + "encryption": "[if(or(not(empty(parameters('cMKManagedServicesKeyName'))), not(empty(parameters('cMKManagedServicesKeyName')))), createObject('entities', createObject('managedServices', if(not(empty(parameters('cMKManagedServicesKeyName'))), createObject('keySource', 'Microsoft.Keyvault', 'keyVaultProperties', createObject('keyVaultUri', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('cMKManagedServicesKeyVaultResourceId'), '/')[2], split(parameters('cMKManagedServicesKeyVaultResourceId'), '/')[4]), 'Microsoft.KeyVault/vaults', last(split(parameters('cMKManagedServicesKeyVaultResourceId'), '/'))), '2023-02-01').vaultUri, 'keyName', parameters('cMKManagedServicesKeyName'), 'keyVersion', if(not(empty(parameters('cMKManagedServicesKeyVersion'))), parameters('cMKManagedServicesKeyVersion'), last(split(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('cMKManagedServicesKeyVaultResourceId'), '/')[2], split(parameters('cMKManagedServicesKeyVaultResourceId'), '/')[4]), 'Microsoft.KeyVault/vaults/keys', split(format('{0}/{1}', last(split(parameters('cMKManagedServicesKeyVaultResourceId'), '/')), parameters('cMKManagedServicesKeyName')), '/')[0], split(format('{0}/{1}', last(split(parameters('cMKManagedServicesKeyVaultResourceId'), '/')), parameters('cMKManagedServicesKeyName')), '/')[1]), '2023-02-01').keyUriWithVersion, '/'))))), null()), 'managedDisk', if(not(empty(parameters('cMKManagedDisksKeyName'))), createObject('keySource', 'Microsoft.Keyvault', 'keyVaultProperties', createObject('keyVaultUri', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('cMKManagedDisksKeyVaultResourceId'), '/')[2], split(parameters('cMKManagedDisksKeyVaultResourceId'), '/')[4]), 'Microsoft.KeyVault/vaults', last(split(parameters('cMKManagedDisksKeyVaultResourceId'), '/'))), '2023-02-01').vaultUri, 'keyName', parameters('cMKManagedDisksKeyName'), 'keyVersion', if(not(empty(parameters('cMKManagedDisksKeyVersion'))), parameters('cMKManagedDisksKeyVersion'), last(split(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('cMKManagedDisksKeyVaultResourceId'), '/')[2], split(parameters('cMKManagedDisksKeyVaultResourceId'), '/')[4]), 'Microsoft.KeyVault/vaults/keys', split(format('{0}/{1}', last(split(parameters('cMKManagedDisksKeyVaultResourceId'), '/')), parameters('cMKManagedDisksKeyName')), '/')[0], split(format('{0}/{1}', last(split(parameters('cMKManagedDisksKeyVaultResourceId'), '/')), parameters('cMKManagedDisksKeyName')), '/')[1]), '2023-02-01').keyUriWithVersion, '/')))), 'rotationToLatestKeyVersionEnabled', parameters('cMKManagedDisksKeyRotationToLatestKeyVersionEnabled')), null()))), null())]" } }, { @@ -202,7 +373,7 @@ ] }, { - "condition": "[and(equals(parameters('pricingTier'), 'premium'), or(or(or(not(empty(parameters('diagnosticStorageAccountId'))), not(empty(parameters('diagnosticWorkspaceId')))), not(empty(parameters('diagnosticEventHubAuthorizationRuleId')))), not(empty(parameters('diagnosticEventHubName')))))]", + "condition": "[and(equals(parameters('skuName'), 'premium'), or(or(or(not(empty(parameters('diagnosticStorageAccountId'))), not(empty(parameters('diagnosticWorkspaceId')))), not(empty(parameters('diagnosticEventHubAuthorizationRuleId')))), not(empty(parameters('diagnosticEventHubName')))))]", "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", "scope": "[format('Microsoft.Databricks/workspaces/{0}', parameters('name'))]", @@ -225,7 +396,7 @@ }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-DataBricks-Rbac-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-Databricks-Rbac-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -365,6 +536,572 @@ "dependsOn": [ "[resourceId('Microsoft.Databricks/workspaces', parameters('name'))]" ] + }, + { + "copy": { + "name": "workspace_privateEndpoints", + "count": "[length(parameters('privateEndpoints'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Databricks-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "groupIds": { + "value": [ + "[parameters('privateEndpoints')[copyIndex()].service]" + ] + }, + "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Databricks/workspaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "serviceResourceId": { + "value": "[resourceId('Microsoft.Databricks/workspaces', parameters('name'))]" + }, + "subnetResourceId": { + "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + }, + "enableDefaultTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "location": { + "value": "[reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location]" + }, + "lock": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'lock'), createObject('value', parameters('privateEndpoints')[copyIndex()].lock), createObject('value', parameters('lock')))]", + "privateDnsZoneGroup": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroup'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroup), createObject('value', createObject()))]", + "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", + "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", + "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", + "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", + "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", + "applicationSecurityGroups": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroups'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroups), createObject('value', createArray()))]", + "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.20.4.51522", + "templateHash": "13560297539192628062" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "serviceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource that needs to be connected to the network." + } + }, + "applicationSecurityGroups": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. Subtype(s) of the connection to be created. The allowed values depend on the type serviceResourceId refers to." + } + }, + "privateDnsZoneGroup": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The private DNS zone group configuration used to associate the private endpoint with one or multiple private DNS zones. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "CanNotDelete", + "ReadOnly" + ], + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableDefaultTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable telemetry via a Globally Unique Identifier (GUID)." + } + } + }, + "variables": { + "enableReferencedModulesTelemetry": false + }, + "resources": [ + { + "condition": "[parameters('enableDefaultTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-{0}', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [] + } + } + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "applicationSecurityGroups": "[parameters('applicationSecurityGroups')]", + "customDnsConfigs": "[parameters('customDnsConfigs')]", + "customNetworkInterfaceName": "[parameters('customNetworkInterfaceName')]", + "ipConfigurations": "[parameters('ipConfigurations')]", + "manualPrivateLinkServiceConnections": "[parameters('manualPrivateLinkServiceConnections')]", + "privateLinkServiceConnections": [ + { + "name": "[parameters('name')]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceId')]", + "groupIds": "[parameters('groupIds')]" + } + } + ], + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + { + "condition": "[not(empty(parameters('lock')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[format('{0}-{1}-lock', parameters('name'), parameters('lock'))]", + "properties": { + "level": "[parameters('lock')]", + "notes": "[if(equals(parameters('lock'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot modify the resource or child resources.')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + ] + }, + { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDNSResourceIds": { + "value": "[parameters('privateDnsZoneGroup').privateDNSResourceIds]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "enableDefaultTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.20.4.51522", + "templateHash": "17831763001460207830" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + }, + "enableDefaultTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable telemetry via a Globally Unique Identifier (GUID)." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "condition": "[parameters('enableDefaultTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-{0}', uniqueString(deployment().name))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [] + } + } + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + ] + }, + { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(parameters('roleAssignments'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-Rbac-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "description": "[if(contains(parameters('roleAssignments')[copyIndex()], 'description'), createObject('value', parameters('roleAssignments')[copyIndex()].description), createObject('value', ''))]", + "principalIds": { + "value": "[parameters('roleAssignments')[copyIndex()].principalIds]" + }, + "principalType": "[if(contains(parameters('roleAssignments')[copyIndex()], 'principalType'), createObject('value', parameters('roleAssignments')[copyIndex()].principalType), createObject('value', ''))]", + "roleDefinitionIdOrName": { + "value": "[parameters('roleAssignments')[copyIndex()].roleDefinitionIdOrName]" + }, + "condition": "[if(contains(parameters('roleAssignments')[copyIndex()], 'condition'), createObject('value', parameters('roleAssignments')[copyIndex()].condition), createObject('value', ''))]", + "delegatedManagedIdentityResourceId": "[if(contains(parameters('roleAssignments')[copyIndex()], 'delegatedManagedIdentityResourceId'), createObject('value', parameters('roleAssignments')[copyIndex()].delegatedManagedIdentityResourceId), createObject('value', ''))]", + "resourceId": { + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.20.4.51522", + "templateHash": "11548486149222715894" + } + }, + "parameters": { + "principalIds": { + "type": "array", + "metadata": { + "description": "Required. The IDs of the principals to assign the role to." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the resource to apply the role assignment to." + } + }, + "principalType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "ServicePrincipal", + "Group", + "User", + "ForeignGroup", + "Device", + "" + ], + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "defaultValue": "2.0", + "allowedValues": [ + "2.0" + ], + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Id of the delegated managed identity resource." + } + } + }, + "variables": { + "builtInRoleNames": { + "Avere Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4f8fab4f-1852-4a58-a46a-8eaf358af14a')]", + "Avere Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c025889f-8102-4ebf-b32c-fc0c6f0c6bd9')]", + "Azure Center for SAP solutions administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7b0c7e81-271f-4c71-90bf-e30bdfdbc2f7')]", + "Azure Center for SAP solutions reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '05352d14-a920-4328-a0de-4cbe7430e26b')]", + "Azure Center for SAP solutions service role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aabbc5dd-1af0-458b-a942-81af88f9c138')]", + "Azure Kubernetes Service Policy Add-on Deployment": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18ed5180-3e48-46fd-8541-4ea054d57064')]", + "Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e467623-bb1f-42f4-a55d-6e525e11384b')]", + "Backup Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00c29273-979b-4161-815c-10b084fb9324')]", + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Cosmos DB Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]", + "Desktop Virtualization Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c')]", + "DevTest Labs User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76283e04-6283-4c54-8f91-bcf1374a3c64')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "DocumentDB Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5bd9cd88-fe45-4216-938b-f97437e15450')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "LocalNGFirewallAdministrator role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a8835c7d-b5cb-47fa-b6f0-65ea10ce07a2')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Managed Application Contributor Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e')]", + "Managed Application Operator Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae')]", + "Managed Applications Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Resource Policy Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Site Recovery Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6670b86e-a3f7-4917-ac9b-5d6ab1be4567')]", + "Site Recovery Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '494ae006-db33-4328-bf46-533a6560a3ca')]", + "SQL Managed Instance Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4939a1f6-9ae0-4e48-a1e0-f2cbe897382d')]", + "SQL Security Manager": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Traffic Manager Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4b10055-b0c7-44c2-b00f-c7b5b3550cf7')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", + "Virtual Machine Administrator Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1c0163c0-47e6-4577-8991-ea5c82e286e4')]", + "Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9980e02c-c2be-4d73-94e8-173b1dc7cf3c')]", + "Virtual Machine User Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb879df8-f326-4884-b1cf-06f3ad86be52')]", + "Windows Admin Center Administrator Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a6333a3e-0164-44c3-b281-7a577aff287f')]" + } + }, + "resources": [ + { + "copy": { + "name": "roleAssignment", + "count": "[length(parameters('principalIds'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', last(split(parameters('resourceId'), '/')))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', last(split(parameters('resourceId'), '/'))), parameters('principalIds')[copyIndex()], parameters('roleDefinitionIdOrName'))]", + "properties": { + "description": "[parameters('description')]", + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), parameters('roleDefinitionIdOrName')), variables('builtInRoleNames')[parameters('roleDefinitionIdOrName')], parameters('roleDefinitionIdOrName'))]", + "principalId": "[parameters('principalIds')[copyIndex()]]", + "principalType": "[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "condition": "[if(not(empty(parameters('condition'))), parameters('condition'), null())]", + "conditionVersion": "[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + ] + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), '2023-04-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Databricks/workspaces', parameters('name'))]" + ] } ], "outputs": { @@ -394,7 +1131,7 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference(resourceId('Microsoft.Databricks/workspaces', parameters('name')), '2018-04-01', 'full').location]" + "value": "[reference(resourceId('Microsoft.Databricks/workspaces', parameters('name')), '2023-02-01', 'full').location]" } } } \ No newline at end of file