From 50c393f9e1034f9141ad1d6e28b24cf7ef708b52 Mon Sep 17 00:00:00 2001 From: Vladimir Lazarenko Date: Thu, 26 Jan 2023 13:57:48 +0100 Subject: [PATCH] `azurerm_databricks_workspace`: Add support for CMK for disk encryption, multiple other updates (#19992) * WIP * `azurerm_databricks_workspace`: Add support for CMK for disk encryption 2 new arguments: `managed_disk_cmk_key_vault_key_id` `managed_disk_cmk_rotation_to_latest_version_enabled` * Add rotation setting * terrafmt * 2 step * Update a few things * Set computed attribute * Rename test to reflect what we're testing better * Update test * Address some of the comments * Allow update of customer_managed_key_enabled * Added managed_disk_identity * Added managed_disk_identity * Set managed_disk_identity rights in diskCMK test * Remove ForceNew from managed_disk_cmk * Changes for no_public_ip * Fix test * set no_public_ip to false in complete config to test update Co-authored-by: Steph --- .../databricks_workspace_data_source.go | 30 +- .../databricks_workspace_resource.go | 148 ++++++-- .../databricks_workspace_resource_test.go | 358 ++++++++++++++++++ .../docs/d/databricks_workspace.html.markdown | 12 + .../docs/r/databricks_workspace.html.markdown | 30 +- 5 files changed, 548 insertions(+), 30 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_data_source.go b/internal/services/databricks/databricks_workspace_data_source.go index 64d2ab07624e..65195043ef8d 100644 --- a/internal/services/databricks/databricks_workspace_data_source.go +++ b/internal/services/databricks/databricks_workspace_data_source.go @@ -46,6 +46,31 @@ func dataSourceDatabricksWorkspace() *pluginsdk.Resource { Computed: true, }, + "managed_disk_identity": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "principal_id": { + Type: pluginsdk.TypeString, + Sensitive: true, + Computed: true, + }, + + "tenant_id": { + Type: pluginsdk.TypeString, + Sensitive: true, + Computed: true, + }, + + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + "storage_account_identity": { Type: pluginsdk.TypeList, Computed: true, @@ -102,9 +127,12 @@ func dataSourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface d.Set("sku", sku.Name) } d.Set("workspace_id", model.Properties.WorkspaceId) - if err := d.Set("storage_account_identity", flattenWorkspaceStorageAccountIdentity(model.Properties.StorageAccountIdentity)); err != nil { + if err := d.Set("storage_account_identity", flattenWorkspaceManagedIdentity(model.Properties.StorageAccountIdentity)); err != nil { return fmt.Errorf("setting `storage_account_identity`: %+v", err) } + if err := d.Set("managed_disk_identity", flattenWorkspaceManagedIdentity(model.Properties.StorageAccountIdentity)); err != nil { + return fmt.Errorf("setting `managed_disk_identity`: %+v", err) + } d.Set("workspace_url", model.Properties.WorkspaceUrl) d.Set("location", model.Location) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index bf95672c4fe3..132af021c092 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -79,7 +79,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { "customer_managed_key_enabled": { Type: pluginsdk.TypeBool, - ForceNew: true, Optional: true, Default: false, }, @@ -87,10 +86,46 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { "managed_services_cmk_key_vault_key_id": { Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, ValidateFunc: keyVaultValidate.KeyVaultChildID, }, + "managed_disk_cmk_key_vault_key_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + }, + + "managed_disk_identity": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "principal_id": { + Type: pluginsdk.TypeString, + Sensitive: true, + Computed: true, + }, + + "tenant_id": { + Type: pluginsdk.TypeString, + Sensitive: true, + Computed: true, + }, + + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "managed_disk_cmk_rotation_to_latest_version_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + RequiredWith: []string{"managed_disk_cmk_key_vault_key_id"}, + }, + "infrastructure_encryption_enabled": { Type: pluginsdk.TypeBool, ForceNew: true, @@ -100,7 +135,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { "public_network_access_enabled": { Type: pluginsdk.TypeBool, - ForceNew: true, Optional: true, Default: true, }, @@ -108,7 +142,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { "network_security_group_rules_required": { Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, Computed: true, ValidateFunc: validation.StringInSlice([]string{ string(workspaces.RequiredNsgRulesAllRules), @@ -149,7 +182,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { "no_public_ip": { Type: pluginsdk.TypeBool, - ForceNew: true, Optional: true, Computed: true, AtLeastOneOf: workspaceCustomParametersString(), @@ -242,6 +274,11 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { Computed: true, }, + "disk_encryption_set_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "storage_account_identity": { Type: pluginsdk.TypeList, Computed: true, @@ -277,6 +314,7 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { _, requireNsgRules := d.GetChange("network_security_group_rules_required") _, backendPool := d.GetChange("load_balancer_backend_address_pool_id") _, managedServicesCMK := d.GetChange("managed_services_cmk_key_vault_key_id") + _, managedDiskCMK := d.GetChange("managed_disk_cmk_key_vault_key_id") oldSku, newSku := d.GetChange("sku") @@ -302,8 +340,8 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { } } - if (customerEncryptionEnabled.(bool) || infrastructureEncryptionEnabled.(bool) || managedServicesCMK.(string) != "") && !strings.EqualFold("premium", newSku.(string)) { - return fmt.Errorf("'customer_managed_key_enabled', 'infrastructure_encryption_enabled' and 'managed_services_cmk_key_vault_key_id' are only available with a 'premium' workspace 'sku', got %q", newSku) + if (customerEncryptionEnabled.(bool) || infrastructureEncryptionEnabled.(bool) || managedServicesCMK.(string) != "" || managedDiskCMK.(string) != "") && !strings.EqualFold("premium", newSku.(string)) { + return fmt.Errorf("'customer_managed_key_enabled', 'infrastructure_encryption_enabled', 'managed_disk_cmk_key_vault_key_id' and 'managed_services_cmk_key_vault_key_id' are only available with a 'premium' workspace 'sku', got %q", newSku) } return nil @@ -408,30 +446,59 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // Set up customer-managed keys for managed services encryption (e.g. notebook) setEncrypt := false encrypt := &workspaces.WorkspacePropertiesEncryption{} - keyIdRaw := d.Get("managed_services_cmk_key_vault_key_id").(string) - if keyIdRaw != "" { + encrypt.Entities = workspaces.EncryptionEntitiesDefinition{} + servicesKeyIdRaw := d.Get("managed_services_cmk_key_vault_key_id").(string) + if servicesKeyIdRaw != "" { setEncrypt = true - key, err := keyVaultParse.ParseNestedItemID(keyIdRaw) + key, err := keyVaultParse.ParseNestedItemID(servicesKeyIdRaw) if err != nil { return err } - encrypt.Entities = workspaces.EncryptionEntitiesDefinition{ - ManagedServices: &workspaces.EncryptionV2{ - // There is only one valid source for this field at this point in time so I have hardcoded the value - KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, - KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ - KeyName: key.Name, - KeyVersion: key.Version, - KeyVaultUri: key.KeyVaultBaseUrl, - }, + // make sure the key vault exists + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, meta.(*clients.Client).Resource, key.KeyVaultBaseUrl) + if err != nil || keyVaultIdRaw == nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) + } + + encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ + // There is only one valid source for this field at this point in time so I have hardcoded the value + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ + KeyName: key.Name, + KeyVersion: key.Version, + KeyVaultUri: key.KeyVaultBaseUrl, }, } + } + + diskKeyIdRaw := d.Get("managed_disk_cmk_key_vault_key_id").(string) + if diskKeyIdRaw != "" { + setEncrypt = true + key, err := keyVaultParse.ParseNestedItemID(diskKeyIdRaw) + if err != nil { + return err + } // make sure the key vault exists keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, meta.(*clients.Client).Resource, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) + } + + encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ + // There is only one valid source for this field at this point in time so I have hardcoded the value + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: workspaces.ManagedDiskEncryptionKeyVaultProperties{ + KeyName: key.Name, + KeyVersion: key.Version, + KeyVaultUri: key.KeyVaultBaseUrl, + }, + } + + rotationEnabled := d.Get("managed_disk_cmk_rotation_to_latest_version_enabled").(bool) + if rotationEnabled { + encrypt.Entities.ManagedDisk.RotationToLatestKeyVersionEnabled = utils.Bool(rotationEnabled) } } @@ -490,8 +557,12 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return fmt.Errorf("setting `custom_parameters`: %+v", err) } - if encrypt != nil && keyIdRaw != "" { - d.Set("managed_services_cmk_key_vault_key_id", keyIdRaw) + if encrypt != nil && servicesKeyIdRaw != "" { + d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) + } + + if encrypt != nil && diskKeyIdRaw != "" { + d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) } return resourceDatabricksWorkspaceRead(d, meta) @@ -570,10 +641,14 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("load_balancer_backend_address_pool_id", backendPoolReadId) } - if err := d.Set("storage_account_identity", flattenWorkspaceStorageAccountIdentity(model.Properties.StorageAccountIdentity)); err != nil { + if err := d.Set("storage_account_identity", flattenWorkspaceManagedIdentity(model.Properties.StorageAccountIdentity)); err != nil { return fmt.Errorf("setting `storage_account_identity`: %+v", err) } + if err := d.Set("managed_disk_identity", flattenWorkspaceManagedIdentity(model.Properties.ManagedDiskIdentity)); err != nil { + return fmt.Errorf("setting `managed_disk_identity`: %+v", err) + } + if model.Properties.WorkspaceUrl != nil { d.Set("workspace_url", model.Properties.WorkspaceUrl) } @@ -601,6 +676,31 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("managed_services_cmk_key_vault_key_id", key.ID()) } } + // customer managed key for managed disk + encryptDiskKeyName := "" + encryptDiskKeyVersion := "" + encryptDiskKeyVaultURI := "" + encryptDiskRotationEnabled := false + + if encryption := model.Properties.Encryption; encryption != nil { + if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { + encryptDiskKeyName = encryptionProps.KeyVaultProperties.KeyName + encryptDiskKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion + encryptDiskKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri + encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled + } + + } + + if encryptDiskKeyVaultURI != "" { + key, err := keyVaultParse.NewNestedItemID(encryptDiskKeyVaultURI, "keys", encryptDiskKeyName, encryptDiskKeyVersion) + if err == nil { + d.Set("managed_disk_cmk_key_vault_key_id", key.ID()) + } + d.Set("managed_disk_cmk_rotation_to_latest_version_enabled", encryptDiskRotationEnabled) + d.Set("disk_encryption_set_id", model.Properties.DiskEncryptionSetId) + } + return tags.FlattenAndSet(d, model.Tags) } @@ -624,7 +724,7 @@ func resourceDatabricksWorkspaceDelete(d *pluginsdk.ResourceData, meta interface return nil } -func flattenWorkspaceStorageAccountIdentity(input *workspaces.ManagedIdentityConfiguration) []interface{} { +func flattenWorkspaceManagedIdentity(input *workspaces.ManagedIdentityConfiguration) []interface{} { if input == nil { return nil } diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index b6a42c118a2c..615d25cfd4e8 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -173,6 +173,28 @@ func TestAccDatabricksWorkspace_update(t *testing.T) { }) } +func TestAccDatabricksWorkspace_extendedUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + r := DatabricksWorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedUpdateCreate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("custom_parameters.0.public_subnet_network_security_group_association_id", "custom_parameters.0.private_subnet_network_security_group_association_id"), + { + Config: r.extendedUpdateUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("custom_parameters.0.public_subnet_network_security_group_association_id", "custom_parameters.0.private_subnet_network_security_group_association_id"), + }) +} + func TestAccDatabricksWorkspace_updateSKU(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") r := DatabricksWorkspaceResource{} @@ -234,6 +256,22 @@ func TestAccDatabricksWorkspace_managedServicesAndDbfsCMK(t *testing.T) { }) } +func TestAccDatabricksWorkspace_managedDiskCMK(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + databricksPrincipalID := getDatabricksPrincipalId(data.Client().SubscriptionID) + r := DatabricksWorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.managedDiskCMK(data, databricksPrincipalID), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("custom_parameters.0.public_subnet_network_security_group_association_id", "custom_parameters.0.private_subnet_network_security_group_association_id"), + }) +} + func TestAccDatabricksWorkspace_managedServicesDbfsCMKAndPrivateLink(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") databricksPrincipalID := getDatabricksPrincipalId(data.Client().SubscriptionID) @@ -412,6 +450,213 @@ resource "azurerm_databricks_workspace" "test" { sku = "standard" managed_resource_group_name = "acctestRG-DBW-%[1]d-managed" + custom_parameters { + no_public_ip = false + public_subnet_name = azurerm_subnet.public.name + private_subnet_name = azurerm_subnet.private.name + virtual_network_id = azurerm_virtual_network.test.id + + public_subnet_network_security_group_association_id = azurerm_subnet_network_security_group_association.public.id + private_subnet_network_security_group_association_id = azurerm_subnet_network_security_group_association.private.id + } + + tags = { + Environment = "Production" + Pricing = "Standard" + } +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (DatabricksWorkspaceResource) extendedUpdateCreate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "public" { + name = "acctest-sn-public-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.1.0/24"] + + delegation { + name = "acctest" + + service_delegation { + name = "Microsoft.Databricks/workspaces" + + actions = [ + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", + "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action", + ] + } + } +} + +resource "azurerm_subnet" "private" { + name = "acctest-sn-private-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] + + delegation { + name = "acctest" + + service_delegation { + name = "Microsoft.Databricks/workspaces" + + actions = [ + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", + "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action", + ] + } + } +} + +resource "azurerm_network_security_group" "nsg" { + name = "acctest-nsg-private-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet_network_security_group_association" "public" { + subnet_id = azurerm_subnet.public.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +resource "azurerm_subnet_network_security_group_association" "private" { + subnet_id = azurerm_subnet.private.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +resource "azurerm_databricks_workspace" "test" { + name = "acctestDBW-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "premium" + managed_resource_group_name = "acctestRG-DBW-%[1]d-managed" + public_network_access_enabled = true + customer_managed_key_enabled = true + + custom_parameters { + no_public_ip = true + public_subnet_name = azurerm_subnet.public.name + private_subnet_name = azurerm_subnet.private.name + virtual_network_id = azurerm_virtual_network.test.id + + public_subnet_network_security_group_association_id = azurerm_subnet_network_security_group_association.public.id + private_subnet_network_security_group_association_id = azurerm_subnet_network_security_group_association.private.id + } + + tags = { + Environment = "Production" + Pricing = "Standard" + } +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (DatabricksWorkspaceResource) extendedUpdateUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "public" { + name = "acctest-sn-public-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.1.0/24"] + + delegation { + name = "acctest" + + service_delegation { + name = "Microsoft.Databricks/workspaces" + + actions = [ + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", + "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action", + ] + } + } +} + +resource "azurerm_subnet" "private" { + name = "acctest-sn-private-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] + + delegation { + name = "acctest" + + service_delegation { + name = "Microsoft.Databricks/workspaces" + + actions = [ + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", + "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action", + ] + } + } +} + +resource "azurerm_network_security_group" "nsg" { + name = "acctest-nsg-private-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet_network_security_group_association" "public" { + subnet_id = azurerm_subnet.public.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +resource "azurerm_subnet_network_security_group_association" "private" { + subnet_id = azurerm_subnet.private.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +resource "azurerm_databricks_workspace" "test" { + name = "acctestDBW-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "premium" + managed_resource_group_name = "acctestRG-DBW-%[1]d-managed" + public_network_access_enabled = false + network_security_group_rules_required = "NoAzureDatabricksRules" + customer_managed_key_enabled = false + custom_parameters { no_public_ip = true public_subnet_name = azurerm_subnet.public.name @@ -1228,6 +1473,119 @@ resource "azurerm_key_vault_access_policy" "databricks" { `, data.RandomInteger, data.Locations.Primary, data.RandomString, databricksPrincipalID) } +func (DatabricksWorkspaceResource) managedDiskCMK(data acceptance.TestData, databricksPrincipalID string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-%[1]d" + location = "%[2]s" +} + +resource "azurerm_databricks_workspace" "test" { + depends_on = [azurerm_key_vault_access_policy.managed] + + name = "acctestDBW-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "premium" + managed_resource_group_name = "acctestRG-DBW-%[1]d-managed" + + customer_managed_key_enabled = true + managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.test.id + + tags = { + Environment = "Production" + Pricing = "Premium" + } +} + +resource "azurerm_key_vault" "test" { + name = "acctest-kv-%[3]s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "test" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + name = "acctest-certificate" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_key_vault_access_policy" "terraform" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_key_vault.test.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Get", + "List", + "Create", + "Decrypt", + "Encrypt", + "Sign", + "UnwrapKey", + "Verify", + "WrapKey", + "Delete", + "Restore", + "Recover", + "Update", + "Purge", + ] +} + +resource "azurerm_key_vault_access_policy" "managed" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_key_vault.test.tenant_id + object_id = "%[4]s" + + key_permissions = [ + "Get", + "List", + "Encrypt", + "Decrypt", + "UnwrapKey", + "WrapKey", + ] +} + +resource "azurerm_key_vault_access_policy" "databricks" { + depends_on = [azurerm_databricks_workspace.test] + + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_databricks_workspace.test.managed_disk_identity.0.tenant_id + object_id = azurerm_databricks_workspace.test.managed_disk_identity.0.principal_id + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + ] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, databricksPrincipalID) +} + func (DatabricksWorkspaceResource) managedServicesDbfsCMKAndPrivateLink(data acceptance.TestData, databricksPrincipalID string) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/d/databricks_workspace.html.markdown b/website/docs/d/databricks_workspace.html.markdown index 5634f25d1bfc..0c01922ff9c5 100644 --- a/website/docs/d/databricks_workspace.html.markdown +++ b/website/docs/d/databricks_workspace.html.markdown @@ -40,12 +40,24 @@ output "databricks_workspace_id" { * `workspace_url` - URL this Databricks Workspace is accessible on. +* `managed_disk_identity` - A `managed_disk_identity` block as documented below. + * `storage_account_identity` - A `storage_account_identity` block as documented below. * `tags` - A mapping of tags to assign to the Databricks Workspace. --- +A `managed_disk_identity` block exports the following: + +* `principal_id` - The principal UUID for the internal databricks disks identity needed to provide access to the workspace for enabling Customer Managed Keys. + +* `tenant_id` - The UUID of the tenant where the internal databricks disks identity was created. + +* `type` - The type of the internal databricks disk identity. + +--- + A `storage_account_identity` block exports the following: * `principal_id` - The principal UUID for the internal databricks storage account needed to provide access to the workspace for enabling Customer Managed Keys. diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index ce694f62f8d0..33cab4e9a164 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -50,19 +50,23 @@ The following arguments are supported: ~> **NOTE** Downgrading to a `trial sku` from a `standard` or `premium sku` will force a new resource to be created. -* `managed_services_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed resources(e.g. Notebooks and Artifacts). Changing this forces a new resource to be created. +* `managed_services_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed resources(e.g. Notebooks and Artifacts). + +* `managed_disk_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed disks. + +* `managed_disk_cmk_rotation_to_latest_version_enabled` - (Optional) Whether customer managed keys for disk encryption will automatically be rotated to the latest version. * `managed_resource_group_name` - (Optional) The name of the resource group where Azure should place the managed Databricks resources. Changing this forces a new resource to be created. ~> **NOTE** Make sure that this field is unique if you have multiple Databrick Workspaces deployed in your subscription and choose to not have the `managed_resource_group_name` auto generated by the Azure Resource Provider. Having multiple Databrick Workspaces deployed in the same subscription with the same `manage_resource_group_name` may result in some resources that cannot be deleted. -* `customer_managed_key_enabled` - (Optional) Is the workspace enabled for customer managed key encryption? If `true` this enables the Managed Identity for the managed storage account. Possible values are `true` or `false`. Defaults to `false`. This field is only valid if the Databricks Workspace `sku` is set to `premium`. Changing this forces a new resource to be created. +* `customer_managed_key_enabled` - (Optional) Is the workspace enabled for customer managed key encryption? If `true` this enables the Managed Identity for the managed storage account. Possible values are `true` or `false`. Defaults to `false`. This field is only valid if the Databricks Workspace `sku` is set to `premium`. * `infrastructure_encryption_enabled` - (Optional) Is the Databricks File System root file system enabled with a secondary layer of encryption with platform managed keys? Possible values are `true` or `false`. Defaults to `false`. This field is only valid if the Databricks Workspace `sku` is set to `premium`. Changing this forces a new resource to be created. -* `public_network_access_enabled` - (Optional) Allow public access for accessing workspace. Set value to `false` to access workspace only via private link endpoint. Possible values include `true` or `false`. Defaults to `true`. Changing this forces a new resource to be created. +* `public_network_access_enabled` - (Optional) Allow public access for accessing workspace. Set value to `false` to access workspace only via private link endpoint. Possible values include `true` or `false`. Defaults to `true`. -* `network_security_group_rules_required` - (Optional) Does the data plane (clusters) to control plane communication happen over private link endpoint only or publicly? Possible values `AllRules`, `NoAzureDatabricksRules` or `NoAzureServiceRules`. Required when `public_network_access_enabled` is set to `false`. Changing this forces a new resource to be created. +* `network_security_group_rules_required` - (Optional) Does the data plane (clusters) to control plane communication happen over private link endpoint only or publicly? Possible values `AllRules`, `NoAzureDatabricksRules` or `NoAzureServiceRules`. Required when `public_network_access_enabled` is set to `false`. * `custom_parameters` - (Optional) A `custom_parameters` block as documented below. @@ -78,7 +82,9 @@ A `custom_parameters` block supports the following: * `public_ip_name` - (Optional) Name of the Public IP for No Public IP workspace with managed vNet. Defaults to `nat-gw-public-ip`. Changing this forces a new resource to be created. -* `no_public_ip` - (Optional) Are public IP Addresses not allowed? Possible values are `true` or `false`. Defaults to `false`. Changing this forces a new resource to be created. +* `no_public_ip` - (Optional) Are public IP Addresses not allowed? Possible values are `true` or `false`. Defaults to `false`. + +~> **NOTE** Updating `no_public_ip` parameter is only allowed if the value is changing from `false` to `true` and and only for VNet-injected workspaces. * `public_subnet_name` - (Optional) The name of the Public Subnet within the Virtual Network. Required if `virtual_network_id` is set. Changing this forces a new resource to be created. @@ -113,6 +119,10 @@ The following attributes are exported: * `id` - The ID of the Databricks Workspace in the Azure management plane. +* `disk_encryption_set_id` - The ID of Managed Disk Encryption Set created by the Databricks Workspace. + +* `managed_disk_identity` - A `managed_disk_identity` block as documented below. + * `managed_resource_group_id` - The ID of the Managed Resource Group created by the Databricks Workspace. * `workspace_url` - The workspace URL which is of the format 'adb-{workspaceId}.{random}.azuredatabricks.net' @@ -123,6 +133,16 @@ The following attributes are exported: --- +A `managed_disk_identity` block exports the following: + +* `principal_id` - The principal UUID for the internal databricks disks identity needed to provide access to the workspace for enabling Customer Managed Keys. + +* `tenant_id` - The UUID of the tenant where the internal databricks disks identity was created. + +* `type` - The type of the internal databricks disks identity. + +--- + A `storage_account_identity` block exports the following: * `principal_id` - The principal UUID for the internal databricks storage account needed to provide access to the workspace for enabling Customer Managed Keys.