From bdf2104b6a611395ba0b62edf0b2508899e1b0cb Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:55:27 -0700 Subject: [PATCH 01/28] Initial Check-in... --- .../databricks_workspace_resource.go | 81 ++++++++++++------- .../docs/r/databricks_workspace.html.markdown | 2 + ...ot_dbfs_customer_managed_key.html.markdown | 4 +- 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index bb7ae00cefc5..12969c96f554 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -88,6 +88,13 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { Default: false, }, + // added to support cross subscription cmk's + "managed_services_cmk_key_vault_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultKeyID, + }, + "managed_services_cmk_key_vault_key_id": { Type: pluginsdk.TypeString, Optional: true, @@ -429,6 +436,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } requireNsgRules := d.Get("network_security_group_rules_required").(string) customParamsRaw := d.Get("custom_parameters").([]interface{}) + managedServicesKeyVaultId := d.Get("managed_services_cmk_key_vault_id").(string) customParams, pubSubAssoc, priSubAssoc := expandWorkspaceCustomParameters(customParamsRaw, customerEncryptionEnabled, infrastructureEncryptionEnabled, backendPoolName, loadBalancerId) if len(customParamsRaw) > 0 && customParamsRaw[0] != nil { @@ -462,11 +470,23 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - // make sure the key vault exists + // if the 'managed_services_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) + + if managedServicesKeyVaultId != "" { + kvId, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) + } + + subscriptionResourceId = commonids.NewSubscriptionID(kvId.SubscriptionId) + } + + // make sure the key vault exists keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, 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 services Key Vault in subscription %q at URL %q: %+v", subscriptionResourceId, key.KeyVaultBaseUrl, err) } encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ @@ -488,11 +508,23 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - // make sure the key vault exists + // if the 'managed_services_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) + + if managedServicesKeyVaultId != "" { + kvId, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) + } + + subscriptionResourceId = commonids.NewSubscriptionID(kvId.SubscriptionId) + } + + // make sure the key vault exists keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk 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 in subscription %q at URL %q: %+v", subscriptionResourceId, key.KeyVaultBaseUrl, err) } encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ @@ -566,13 +598,9 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return fmt.Errorf("setting `custom_parameters`: %+v", err) } - 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) - } + d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) + d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) + d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) return resourceDatabricksWorkspaceRead(d, meta) } @@ -710,6 +738,9 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("disk_encryption_set_id", model.Properties.DiskEncryptionSetId) } + managedServicesKeyVaultId := d.Get("managed_services_cmk_key_vault_id").(string) + d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + return tags.FlattenAndSet(d, model.Tags) } @@ -740,26 +771,20 @@ func flattenWorkspaceManagedIdentity(input *workspaces.ManagedIdentityConfigurat e := make(map[string]interface{}) - if v := input; v != nil { - if t := v.PrincipalId; t != nil { - if t != nil { - e["principal_id"] = *t - } - } + if t := input.PrincipalId; t != nil { + e["principal_id"] = *t + } - if t := v.TenantId; t != nil { - e["tenant_id"] = *t - } + if t := input.TenantId; t != nil { + e["tenant_id"] = *t + } - if t := v.Type; t != nil { - if t != nil { - e["type"] = *t - } - } + if t := input.Type; t != nil { + e["type"] = *t + } - if len(e) != 0 { - return []interface{}{e} - } + if len(e) != 0 { + return []interface{}{e} } return []interface{}{e} diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index b01783b4ba97..57673782fc54 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -48,6 +48,8 @@ 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_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` keys. + * `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. diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index 6dbe1f17e71c..a1fa2771fbc8 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -3,12 +3,12 @@ subcategory: "Databricks" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_databricks_workspace_root_dbfs_customer_managed_key" description: |- - Manages a Customer Managed Key for the Databricks Workspaces root Databricks File System(DBFS) + Manages a Customer Managed Key for the Databricks Workspaces Root Databricks File System(DBFS) --- # azurerm_databricks_workspace_root_dbfs_customer_managed_key -Manages a Customer Managed Key for the Databricks Workspaces root Databricks File System(DBFS) +Manages a Customer Managed Key for the Databricks Workspaces Root Databricks File System(DBFS) ## Example Usage From 519ded10b772b1e82257569c38fe6aaa2885f01c Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:09:21 -0700 Subject: [PATCH 02/28] Update ValidateFunc for new field... --- internal/services/databricks/databricks_workspace_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 12969c96f554..522769c01798 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -92,7 +92,7 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { "managed_services_cmk_key_vault_id": { Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: commonids.ValidateKeyVaultKeyID, + ValidateFunc: commonids.ValidateKeyVaultID, }, "managed_services_cmk_key_vault_key_id": { From 1d7dcd838db73eeacef147a8a6f389e8002eea28 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:13:52 -0700 Subject: [PATCH 03/28] Updated the name of new field... --- .../databricks/databricks_workspace_resource.go | 14 +++++++------- website/docs/r/databricks_workspace.html.markdown | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 522769c01798..ef77e372be44 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -89,7 +89,7 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { }, // added to support cross subscription cmk's - "managed_services_cmk_key_vault_id": { + "managed_cmk_key_vault_id": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: commonids.ValidateKeyVaultID, @@ -436,7 +436,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } requireNsgRules := d.Get("network_security_group_rules_required").(string) customParamsRaw := d.Get("custom_parameters").([]interface{}) - managedServicesKeyVaultId := d.Get("managed_services_cmk_key_vault_id").(string) + managedServicesKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) customParams, pubSubAssoc, priSubAssoc := expandWorkspaceCustomParameters(customParamsRaw, customerEncryptionEnabled, infrastructureEncryptionEnabled, backendPoolName, loadBalancerId) if len(customParamsRaw) > 0 && customParamsRaw[0] != nil { @@ -470,7 +470,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - // if the 'managed_services_cmk_key_vault_id' was not defined assume + // if the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) @@ -508,7 +508,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - // if the 'managed_services_cmk_key_vault_id' was not defined assume + // if the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) @@ -600,7 +600,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) - d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + d.Set("managed_cmk_key_vault_id", managedServicesKeyVaultId) return resourceDatabricksWorkspaceRead(d, meta) } @@ -738,8 +738,8 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("disk_encryption_set_id", model.Properties.DiskEncryptionSetId) } - managedServicesKeyVaultId := d.Get("managed_services_cmk_key_vault_id").(string) - d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + managedServicesKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + d.Set("managed_cmk_key_vault_id", managedServicesKeyVaultId) return tags.FlattenAndSet(d, model.Tags) } diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 57673782fc54..cd68aa2fa923 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -48,7 +48,7 @@ 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_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` keys. +* `managed_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` keys. * `managed_services_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed resources(e.g. Notebooks and Artifacts). From 502bef01e109ca0ffa2260c0927ce6a3af9162b7 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 28 Feb 2024 23:05:30 -0700 Subject: [PATCH 04/28] Add note to documentation... --- website/docs/r/databricks_workspace.html.markdown | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index cd68aa2fa923..46e668a9244c 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -46,10 +46,12 @@ The following arguments are supported: * `sku` - (Required) The `sku` to use for the Databricks Workspace. Possible values are `standard`, `premium`, or `trial`. -~> **NOTE** Downgrading to a `trial sku` from a `standard` or `premium sku` will force a new resource to be created. +~> **Note:** Downgrading to a `trial sku` from a `standard` or `premium sku` will force a new resource to be created. * `managed_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` keys. +~> **Note:** The `managed_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_cmk_key_vault_id` field is defined in the configuration file both `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` must be hosted in the same Key Vault resource which belongs to a different subscription than the Databricks Workspace. + * `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. @@ -58,7 +60,7 @@ The following arguments are supported: * `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. +~> **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`. @@ -84,7 +86,7 @@ A `custom_parameters` block supports the following: * `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. +~> **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. @@ -102,7 +104,7 @@ A `custom_parameters` block supports the following: * `vnet_address_prefix` - (Optional) Address prefix for Managed virtual network. Defaults to `10.139`. Changing this forces a new resource to be created. -~> **NOTE** Databricks requires that a network security group is associated with the `public` and `private` subnets when a `virtual_network_id` has been defined. Both `public` and `private` subnets must be delegated to `Microsoft.Databricks/workspaces`. For more information about subnet delegation see the [product documentation](https://docs.microsoft.com/azure/virtual-network/subnet-delegation-overview). +~> **Note:** Databricks requires that a network security group is associated with the `public` and `private` subnets when a `virtual_network_id` has been defined. Both `public` and `private` subnets must be delegated to `Microsoft.Databricks/workspaces`. For more information about subnet delegation see the [product documentation](https://docs.microsoft.com/azure/virtual-network/subnet-delegation-overview). ## Example HCL Configurations From 7a9be25dc760a7ab66910ae3a474ef3215ceb75c Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 28 Feb 2024 23:19:22 -0700 Subject: [PATCH 05/28] Update var name to align with new field name... --- .../databricks_workspace_resource.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index ef77e372be44..f154cdc413f3 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -436,7 +436,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } requireNsgRules := d.Get("network_security_group_rules_required").(string) customParamsRaw := d.Get("custom_parameters").([]interface{}) - managedServicesKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) customParams, pubSubAssoc, priSubAssoc := expandWorkspaceCustomParameters(customParamsRaw, customerEncryptionEnabled, infrastructureEncryptionEnabled, backendPoolName, loadBalancerId) if len(customParamsRaw) > 0 && customParamsRaw[0] != nil { @@ -474,10 +474,10 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedServicesKeyVaultId != "" { - kvId, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) + if managedKeyVaultId != "" { + kvId, err := commonids.ParseKeyVaultID(managedKeyVaultId) if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) } subscriptionResourceId = commonids.NewSubscriptionID(kvId.SubscriptionId) @@ -512,10 +512,10 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedServicesKeyVaultId != "" { - kvId, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) + if managedKeyVaultId != "" { + kvId, err := commonids.ParseKeyVaultID(managedKeyVaultId) if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) } subscriptionResourceId = commonids.NewSubscriptionID(kvId.SubscriptionId) @@ -600,7 +600,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) - d.Set("managed_cmk_key_vault_id", managedServicesKeyVaultId) + d.Set("managed_cmk_key_vault_id", managedKeyVaultId) return resourceDatabricksWorkspaceRead(d, meta) } @@ -738,8 +738,8 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("disk_encryption_set_id", model.Properties.DiskEncryptionSetId) } - managedServicesKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) - d.Set("managed_cmk_key_vault_id", managedServicesKeyVaultId) + managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + d.Set("managed_cmk_key_vault_id", managedKeyVaultId) return tags.FlattenAndSet(d, model.Tags) } From b0bbba86c6acf86afbefe1d93aff62c821f67d4e Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 28 Feb 2024 23:31:24 -0700 Subject: [PATCH 06/28] Remove redundant validation... --- .../databricks/databricks_workspace_resource.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index f154cdc413f3..fb24572b0a1b 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -473,14 +473,8 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // if the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedKeyVaultId != "" { - kvId, err := commonids.ParseKeyVaultID(managedKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) - } - - subscriptionResourceId = commonids.NewSubscriptionID(kvId.SubscriptionId) + subscriptionResourceId = commonids.NewSubscriptionID(managedKeyVaultId) } // make sure the key vault exists @@ -511,14 +505,8 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // if the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedKeyVaultId != "" { - kvId, err := commonids.ParseKeyVaultID(managedKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) - } - - subscriptionResourceId = commonids.NewSubscriptionID(kvId.SubscriptionId) + subscriptionResourceId = commonids.NewSubscriptionID(managedKeyVaultId) } // make sure the key vault exists From ed974367ff4ab684922bb0ba2de635661eabcef4 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:42:32 -0700 Subject: [PATCH 07/28] Update read function set values even if nil... --- .../databricks_workspace_resource.go | 88 +++++++++++-------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index fb24572b0a1b..1b0c5a531c10 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -463,6 +463,21 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int encrypt := &workspaces.WorkspacePropertiesEncryption{} encrypt.Entities = workspaces.EncryptionEntitiesDefinition{} servicesKeyIdRaw := d.Get("managed_services_cmk_key_vault_key_id").(string) + + // if the 'managed_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... + subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) + if managedKeyVaultId != "" { + // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(managedKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) + } + + subscriptionResourceId = commonids.NewSubscriptionID(v.SubscriptionId) + } + if servicesKeyIdRaw != "" { setEncrypt = true key, err := keyVaultParse.ParseNestedItemID(servicesKeyIdRaw) @@ -470,13 +485,6 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - // if the 'managed_cmk_key_vault_id' was not defined assume - // the key vault exists in the same subscription as the workspace... - subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedKeyVaultId != "" { - subscriptionResourceId = commonids.NewSubscriptionID(managedKeyVaultId) - } - // make sure the key vault exists keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { @@ -502,13 +510,6 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - // if the 'managed_cmk_key_vault_id' was not defined assume - // the key vault exists in the same subscription as the workspace... - subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedKeyVaultId != "" { - subscriptionResourceId = commonids.NewSubscriptionID(managedKeyVaultId) - } - // make sure the key vault exists keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { @@ -586,6 +587,8 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return fmt.Errorf("setting `custom_parameters`: %+v", err) } + // Always set these even if they are empty to keep the state file + // consistent with the configuration file... d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) d.Set("managed_cmk_key_vault_id", managedKeyVaultId) @@ -674,38 +677,47 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} return fmt.Errorf("setting `managed_disk_identity`: %+v", err) } + var workspaceUrl string if model.Properties.WorkspaceUrl != nil { - d.Set("workspace_url", model.Properties.WorkspaceUrl) + workspaceUrl = *model.Properties.WorkspaceUrl } + d.Set("workspace_url", workspaceUrl) + var workspaceId string if model.Properties.WorkspaceId != nil { - d.Set("workspace_id", model.Properties.WorkspaceId) + workspaceId = *model.Properties.WorkspaceId } + d.Set("workspace_id", workspaceId) // customer managed key for managed services - encryptKeyName := "" - encryptKeyVersion := "" - encryptKeyVaultURI := "" + var encryptKeyName string + var encryptKeyVersion string + var encryptKeyVaultURI string + var managedServicesKeyId string if encryption := model.Properties.Encryption; encryption != nil { if encryptionProps := encryption.Entities.ManagedServices; encryptionProps != nil { encryptKeyName = encryptionProps.KeyVaultProperties.KeyName encryptKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion encryptKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri - } - } - if encryptKeyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(encryptKeyVaultURI, keyVaultParse.NestedItemTypeKey, encryptKeyName, encryptKeyVersion) - if err == nil { - d.Set("managed_services_cmk_key_vault_key_id", key.ID()) + if encryptKeyVaultURI != "" { + key, err := keyVaultParse.NewNestedItemID(encryptKeyVaultURI, keyVaultParse.NestedItemTypeKey, encryptKeyName, encryptKeyVersion) + if err == nil { + managedServicesKeyId = key.ID() + } + } } } + d.Set("managed_services_cmk_key_vault_key_id", managedServicesKeyId) + // customer managed key for managed disk - encryptDiskKeyName := "" - encryptDiskKeyVersion := "" - encryptDiskKeyVaultURI := "" - encryptDiskRotationEnabled := false + var encryptDiskKeyName string + var encryptDiskKeyVersion string + var encryptDiskKeyVaultURI string + var encryptDiskEncryptionSetId string + var managedDiskKeyId string + var encryptDiskRotationEnabled bool if encryption := model.Properties.Encryption; encryption != nil { if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { @@ -715,16 +727,20 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled } + if encryptDiskKeyVaultURI != "" { + key, err := keyVaultParse.NewNestedItemID(encryptDiskKeyVaultURI, keyVaultParse.NestedItemTypeKey, encryptDiskKeyName, encryptDiskKeyVersion) + if err == nil { + managedDiskKeyId = key.ID() + } + } } + d.Set("managed_disk_cmk_rotation_to_latest_version_enabled", encryptDiskRotationEnabled) + d.Set("managed_disk_cmk_key_vault_key_id", managedDiskKeyId) - if encryptDiskKeyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(encryptDiskKeyVaultURI, keyVaultParse.NestedItemTypeKey, 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) + if model.Properties.DiskEncryptionSetId != nil { + encryptDiskEncryptionSetId = *model.Properties.DiskEncryptionSetId } + d.Set("disk_encryption_set_id", encryptDiskEncryptionSetId) managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) d.Set("managed_cmk_key_vault_id", managedKeyVaultId) From 25fb094c2048874924e37f2354fc3ae9a914eb29 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 1 Mar 2024 19:12:10 -0700 Subject: [PATCH 08/28] Update var name... --- ...bricks_root_dbfs_customer_managed_key_resource.go | 4 ++++ .../databricks/databricks_workspace_resource.go | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index a07439f6c789..74e250ebe742 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -82,6 +82,10 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return err } + // TODO: Update this code to also be subscription aware? + // or should we just update the documentation to state they + // should use an aliased provider block for this resource + // if the key vault key exists in a different subscription. keyIdRaw := d.Get("key_vault_key_id").(string) key, err := keyVaultParse.ParseNestedItemID(keyIdRaw) if err != nil { diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 1b0c5a531c10..ae22fd87cc19 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -466,7 +466,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // if the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... - subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId) + resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) if managedKeyVaultId != "" { // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID // to extract the correct key vault subscription for the exists call... @@ -475,7 +475,7 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) } - subscriptionResourceId = commonids.NewSubscriptionID(v.SubscriptionId) + resourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) } if servicesKeyIdRaw != "" { @@ -486,9 +486,9 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", subscriptionResourceId, key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", resourceSubscriptionId, key.KeyVaultBaseUrl, err) } encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ @@ -511,9 +511,9 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", subscriptionResourceId, key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", resourceSubscriptionId, key.KeyVaultBaseUrl, err) } encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ From 012b92552af84223dd819cca820f4c5f02541127 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 1 Mar 2024 20:33:05 -0700 Subject: [PATCH 09/28] Add new example for cross subscription... --- .../customer-managed-key/dbfs/main.tf | 8 +- .../managed-services/README.md | 4 +- .../managed-services/main.tf | 38 +++--- .../README.md | 9 ++ .../main.tf | 116 ++++++++++++++++++ .../variables.tf | 6 + .../docs/r/databricks_workspace.html.markdown | 1 + 7 files changed, 159 insertions(+), 23 deletions(-) create mode 100644 examples/databricks/managed-services-cross-subscription/README.md create mode 100644 examples/databricks/managed-services-cross-subscription/main.tf create mode 100644 examples/databricks/managed-services-cross-subscription/variables.tf diff --git a/examples/databricks/customer-managed-key/dbfs/main.tf b/examples/databricks/customer-managed-key/dbfs/main.tf index 92907d8c0cdd..92335865fa47 100644 --- a/examples/databricks/customer-managed-key/dbfs/main.tf +++ b/examples/databricks/customer-managed-key/dbfs/main.tf @@ -82,6 +82,8 @@ resource "azurerm_key_vault_access_policy" "terraform" { "Recover", "Update", "Purge", + "GetRotationPolicy", + "SetRotationPolicy", ] } @@ -93,8 +95,8 @@ resource "azurerm_key_vault_access_policy" "databricks" { object_id = azurerm_databricks_workspace.example.storage_account_identity.0.principal_id key_permissions = [ - "get", - "unwrapKey", - "wrapKey", + "Get", + "UnwrapKey", + "WrapKey", ] } diff --git a/examples/databricks/customer-managed-key/managed-services/README.md b/examples/databricks/customer-managed-key/managed-services/README.md index 82c34ab73ba8..0f820b334b86 100644 --- a/examples/databricks/customer-managed-key/managed-services/README.md +++ b/examples/databricks/customer-managed-key/managed-services/README.md @@ -1,8 +1,8 @@ -## Example: Databricks Workspace Customer Managed Keys for Managed Services +## Example: Databricks Workspace with Customer Managed Keys for Managed Services This example provisions a Databricks Workspace within Azure with Customer Managed Keys for Managed Services enabled. -To find the correct Object ID to use for the `azurerm_key_vault_access_policy.managed` `object_id` field in your configuration file you will need to go to [portal](https://portal.azure.com/) -> `Azure Active Directory` and in the `search your tenant` bar enter the value `2ff814a6-3304-4ab8-85cb-cd0e6f879c1d`. You will see under `Enterprise application` results `AzureDatabricks`, click on the `AzureDatabricks` search result. This will open the `Enterprise Application` overview blade where you will see three values, the name of the application, the application ID, and the object ID. The value you want is the object ID, copy this value and paste it into the `object_id` field for your `azurerm_key_vault_access_policy.managed` configuration block. +To find the correct Object ID to use for the `azurerm_key_vault_access_policy.managed` `object_id` field in your configuration file you will need to go to [portal](https://portal.azure.com/) -> `Microsoft Entra ID` and in the `search your tenant` bar enter the value `2ff814a6-3304-4ab8-85cb-cd0e6f879c1d`. You will see under `Enterprise application` results `AzureDatabricks`, click on the `AzureDatabricks` search result. This will open the `Enterprise Application` overview blade where you will see three values, the name of the application, the application ID, and the object ID. The value you want is the object ID, copy this value and paste it into the `object_id` field for your `azurerm_key_vault_access_policy.managed` configuration block. ### Variables diff --git a/examples/databricks/customer-managed-key/managed-services/main.tf b/examples/databricks/customer-managed-key/managed-services/main.tf index d191a67b546d..ad4337566cea 100644 --- a/examples/databricks/customer-managed-key/managed-services/main.tf +++ b/examples/databricks/customer-managed-key/managed-services/main.tf @@ -24,7 +24,7 @@ resource "azurerm_databricks_workspace" "example" { managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.example.id tags = { - Environment = "Production" + Environment = "Sandbox" } } @@ -62,20 +62,22 @@ resource "azurerm_key_vault_access_policy" "terraform" { 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", + "Get", + "List", + "Create", + "Decrypt", + "Encrypt", + "Sign", + "UnwrapKey", + "Verify", + "WrapKey", + "Delete", + "Restore", + "Recover", + "Update", + "Purge", + "GetRotationPolicy", + "SetRotationPolicy", ] } @@ -85,8 +87,8 @@ resource "azurerm_key_vault_access_policy" "managed" { object_id = "See the README.md file for instructions on how to lookup the correct value to enter here" key_permissions = [ - "get", - "unwrapKey", - "wrapKey", + "Get", + "UnwrapKey", + "WrapKey", ] } diff --git a/examples/databricks/managed-services-cross-subscription/README.md b/examples/databricks/managed-services-cross-subscription/README.md new file mode 100644 index 000000000000..ba98db907ff9 --- /dev/null +++ b/examples/databricks/managed-services-cross-subscription/README.md @@ -0,0 +1,9 @@ +## Example: Databricks Workspace with Customer Managed Keys for Managed Services with Key Vault and Key in a Different Subscription + +This example provisions a Databricks Workspace within Azure with Customer Managed Keys for Managed Services enabled where the Key Vault and Key are hosted in a different subscription within the same tenant. + +To find the correct Object ID to use for the `azurerm_key_vault_access_policy.managed` `object_id` field in your configuration file you will need to go to [portal](https://portal.azure.com/) -> `Microsoft Entra ID` and in the `search your tenant` bar enter the value `2ff814a6-3304-4ab8-85cb-cd0e6f879c1d`. You will see under `Enterprise application` results `AzureDatabricks`, click on the `AzureDatabricks` search result. This will open the `Enterprise Application` overview blade where you will see three values, the name of the application, the application ID, and the object ID. The value you want is the object ID, copy this value and paste it into the `object_id` field for your `azurerm_key_vault_access_policy.managed` configuration block. + +### Variables + +* `prefix` - (Required) The prefix used for all resources in this example. diff --git a/examples/databricks/managed-services-cross-subscription/main.tf b/examples/databricks/managed-services-cross-subscription/main.tf new file mode 100644 index 000000000000..91fb4620b4c1 --- /dev/null +++ b/examples/databricks/managed-services-cross-subscription/main.tf @@ -0,0 +1,116 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "azurerm" { + features {} +} + +provider "azurerm" { + features {} + alias = "keyVaultSubscription" + subscription_id = "{subscription where the Key Vault should be hosted}" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "${var.prefix}-databricks-managed-services" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVault" { + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-databricks-managed-services" + location = "West Europe" +} + +resource "azurerm_databricks_workspace" "example" { + depends_on = [azurerm_key_vault_access_policy.managed] + + name = "${var.prefix}-DBW" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "premium" + managed_resource_group_name = "${var.prefix}-DBW-managed-services" + + managed_cmk_key_vault_id = azurerm_key_vault.example.id + managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.example.id + + tags = { + Environment = "Sandbox" + } +} + +resource "azurerm_key_vault" "example" { + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-keyvault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "example" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-certificate" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_key_vault_access_policy" "terraform" { + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.example.id + tenant_id = azurerm_key_vault.example.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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "managed" { + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.example.id + tenant_id = azurerm_key_vault.example.tenant_id + object_id = "See the README.md file for instructions on how to lookup the correct value to enter here" + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + ] +} diff --git a/examples/databricks/managed-services-cross-subscription/variables.tf b/examples/databricks/managed-services-cross-subscription/variables.tf new file mode 100644 index 000000000000..d48592b4e9a1 --- /dev/null +++ b/examples/databricks/managed-services-cross-subscription/variables.tf @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +variable "prefix" { + description = "The Prefix used for all resources in this example" +} diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 46e668a9244c..788765654399 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -114,6 +114,7 @@ A `custom_parameters` block supports the following: * [Databricks Workspace with Private Endpoint, Customer Managed Keys for Managed Services and Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/private-endpoint/databricks/managed-services) * [Databricks Workspace with Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs) * [Databricks Workspace with Customer Managed Keys for Managed Services](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/managed-services) +* [Databricks Workspace with Customer Managed Keys for Managed Services with Key Vault and Key in a Different Subscription](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/managed-services-cross-subscription) ## Attributes Reference From 0ebd9d271cdb82b9acfa948fedad499a16240140 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:30:17 -0700 Subject: [PATCH 10/28] Expose managed_cmk_key_vault_id in azurerm_databricks_workspace_root_dbfs_customer_managed_key --- .../dbfs-cross-subscription/README.md | 7 + .../dbfs-cross-subscription/main.tf | 124 ++++++++++++++++++ .../dbfs-cross-subscription/variables.tf | 6 + .../customer-managed-key/dbfs/README.md | 2 +- ...root_dbfs_customer_managed_key_resource.go | 57 +++++++- .../docs/r/databricks_workspace.html.markdown | 3 +- ...ot_dbfs_customer_managed_key.html.markdown | 7 +- 7 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 examples/databricks/customer-managed-key/dbfs-cross-subscription/README.md create mode 100644 examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf create mode 100644 examples/databricks/customer-managed-key/dbfs-cross-subscription/variables.tf diff --git a/examples/databricks/customer-managed-key/dbfs-cross-subscription/README.md b/examples/databricks/customer-managed-key/dbfs-cross-subscription/README.md new file mode 100644 index 000000000000..c385f158e42e --- /dev/null +++ b/examples/databricks/customer-managed-key/dbfs-cross-subscription/README.md @@ -0,0 +1,7 @@ +## Example: Databricks Workspace with Root Databricks File System Customer Managed Keys in a Different Subscription + +This example provisions a Databricks Workspace within Azure with Root Databricks File System Customer Managed Keys enabled where the Key Vault and Key are hosted in a different subscription within the same tenant. + +### Variables + +* `prefix` - (Required) The prefix used for all resources in this example. diff --git a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf new file mode 100644 index 000000000000..6c49053faf5a --- /dev/null +++ b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf @@ -0,0 +1,124 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "azurerm" { + features {} +} + +provider "azurerm" { + features {} + alias = "keyVaultSubscription" + subscription_id = "{subscription where the Key Vault should be hosted}" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "${var.prefix}-databricks-cmk" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVault" { + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-databricks-cmk" + location = "West Europe" +} + +resource "azurerm_databricks_workspace" "example" { + name = "${var.prefix}-DBW" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "premium" + managed_resource_group_name = "${var.prefix}-DBW-managed-dbfs" + + customer_managed_key_enabled = true + + tags = { + Environment = "Sandbox" + } +} + +resource "azurerm_databricks_workspace_root_dbfs_customer_managed_key" "example" { + depends_on = [azurerm_key_vault_access_policy.databricks] + + workspace_id = azurerm_databricks_workspace.example.id + key_vault_key_id = azurerm_key_vault_key.example.id + managed_cmk_key_vault_id = azurerm_key_vault.example.id +} + +resource "azurerm_key_vault" "example" { + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-keyvault" + location = azurerm_resource_group.keyVault.location + resource_group_name = azurerm_resource_group.keyVault.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + purge_protection_enabled = true + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "example" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-certificate" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_key_vault_access_policy" "terraform" { + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.example.id + tenant_id = azurerm_key_vault.example.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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "databricks" { + depends_on = [azurerm_databricks_workspace.example] + + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.example.id + tenant_id = azurerm_databricks_workspace.example.storage_account_identity.0.tenant_id + object_id = azurerm_databricks_workspace.example.storage_account_identity.0.principal_id + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + ] +} diff --git a/examples/databricks/customer-managed-key/dbfs-cross-subscription/variables.tf b/examples/databricks/customer-managed-key/dbfs-cross-subscription/variables.tf new file mode 100644 index 000000000000..d48592b4e9a1 --- /dev/null +++ b/examples/databricks/customer-managed-key/dbfs-cross-subscription/variables.tf @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +variable "prefix" { + description = "The Prefix used for all resources in this example" +} diff --git a/examples/databricks/customer-managed-key/dbfs/README.md b/examples/databricks/customer-managed-key/dbfs/README.md index fd3e3bb9cf42..101a11a3d1a1 100644 --- a/examples/databricks/customer-managed-key/dbfs/README.md +++ b/examples/databricks/customer-managed-key/dbfs/README.md @@ -1,4 +1,4 @@ -## Example: Databricks Workspace Root Databricks File System Customer Managed Keys +## Example: Databricks Workspace with Root Databricks File System Customer Managed Keys This example provisions a Databricks Workspace within Azure with Root Databricks File System Customer Managed Keys enabled. diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index 74e250ebe742..f8ba92fe1e9d 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -67,6 +67,13 @@ func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource Required: true, ValidateFunc: keyVaultValidate.KeyVaultChildID, }, + + // added to support cross subscription cmk's + "managed_cmk_key_vault_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + }, }, } } @@ -126,9 +133,24 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } + // if the 'managed_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... + resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + + if managedKeyVaultId != "" { + // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(managedKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) + } + + resourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + } + // make sure the key vault exists - subscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) } @@ -161,6 +183,11 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa } d.SetId(id.ID()) + + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } @@ -225,6 +252,9 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData } } + managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + return nil } @@ -274,11 +304,26 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } + // if the 'managed_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... + resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + + if managedKeyVaultId != "" { + // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(managedKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) + } + + resourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + } + // make sure the key vault exists - subscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", resourceSubscriptionId, key.KeyVaultBaseUrl, err) } // We need to pull all of the custom params from the parent @@ -303,6 +348,8 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("updating Root DBFS Customer Managed Key for %s: %+v", *id, err) } + d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 788765654399..257a61b3ee34 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -112,7 +112,8 @@ A `custom_parameters` block supports the following: * [Databricks Workspace Secure Connectivity Cluster without Load Balancer](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/secure-connectivity-cluster/without-load-balancer) * [Databricks Workspace with Private Endpoint](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/private-endpoint/databricks/private-endpoint) * [Databricks Workspace with Private Endpoint, Customer Managed Keys for Managed Services and Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/private-endpoint/databricks/managed-services) -* [Databricks Workspace with Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs) +* [Databricks Workspace with Root Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs) +* [Databricks Workspace with Root Databricks File System Customer Managed Keys in a Different Subscription](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs-cross-subscription) * [Databricks Workspace with Customer Managed Keys for Managed Services](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/managed-services) * [Databricks Workspace with Customer Managed Keys for Managed Services with Key Vault and Key in a Different Subscription](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/managed-services-cross-subscription) diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index a1fa2771fbc8..a138c57e9ff1 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -112,8 +112,7 @@ resource "azurerm_key_vault_access_policy" "databricks" { ## Example HCL Configurations * [Databricks Workspace with Root Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs) -* [Databricks Workspace with Customer Managed Keys for Managed Services](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/managed-services) -* [Databricks Workspace with Private Endpoint, Customer Managed Keys for Managed Services and Root Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/private-endpoint/databricks/managed-services) +* [Databricks Workspace with Root Databricks File System Customer Managed Keys in a Different Subscription](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs-cross-subscription) ## Argument Reference @@ -123,6 +122,10 @@ The following arguments are supported: * `key_vault_key_id` - (Required) The resource ID of the Key Vault Key to be used. +* `managed_cmk_key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. + +~> **Note:** The `managed_cmk_key_vault_id` field only needs to be specified if the Key Vault wich contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `managed_cmk_key_vault_id` field is not specified the current subscriptioin will be used. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: From 47b388b28aa310761c0cef1133c7b37a06ff04ee Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:49:18 -0700 Subject: [PATCH 11/28] Fix documentation typo... --- ...ricks_workspace_root_dbfs_customer_managed_key.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index a138c57e9ff1..bef178a71e5a 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -124,7 +124,7 @@ The following arguments are supported: * `managed_cmk_key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. -~> **Note:** The `managed_cmk_key_vault_id` field only needs to be specified if the Key Vault wich contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `managed_cmk_key_vault_id` field is not specified the current subscriptioin will be used. +~> **Note:** The `managed_cmk_key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `managed_cmk_key_vault_id` field is not specified the current subscriptioin will be used. ## Attributes Reference From a5ce3ff71ec3ef0c00cef4587a7550e4303a052a Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:56:02 -0700 Subject: [PATCH 12/28] Remove TODO comment from code... --- .../databricks_root_dbfs_customer_managed_key_resource.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index f8ba92fe1e9d..8f74c83d85f9 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -89,10 +89,6 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return err } - // TODO: Update this code to also be subscription aware? - // or should we just update the documentation to state they - // should use an aliased provider block for this resource - // if the key vault key exists in a different subscription. keyIdRaw := d.Get("key_vault_key_id").(string) key, err := keyVaultParse.ParseNestedItemID(keyIdRaw) if err != nil { @@ -133,7 +129,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // if the 'managed_cmk_key_vault_id' was not defined assume + // If the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) @@ -304,7 +300,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // if the 'managed_cmk_key_vault_id' was not defined assume + // If the 'managed_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) From 9dd31b7a2657e0d23cf4ea906677009245fc74eb Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Sat, 2 Mar 2024 00:20:18 -0700 Subject: [PATCH 13/28] Fix documentation object_id lint error... --- .../managed-services/main.tf | 2 +- .../main.tf | 2 +- .../databricks/managed-services/main.tf | 44 ++++++++++--------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/examples/databricks/customer-managed-key/managed-services/main.tf b/examples/databricks/customer-managed-key/managed-services/main.tf index ad4337566cea..83814c9849c0 100644 --- a/examples/databricks/customer-managed-key/managed-services/main.tf +++ b/examples/databricks/customer-managed-key/managed-services/main.tf @@ -84,7 +84,7 @@ resource "azurerm_key_vault_access_policy" "terraform" { resource "azurerm_key_vault_access_policy" "managed" { key_vault_id = azurerm_key_vault.example.id tenant_id = azurerm_key_vault.example.tenant_id - object_id = "See the README.md file for instructions on how to lookup the correct value to enter here" + object_id = "00000000-0000-0000-0000-000000000000" # See the README.md file for instructions on how to lookup the correct value to enter here. key_permissions = [ "Get", diff --git a/examples/databricks/managed-services-cross-subscription/main.tf b/examples/databricks/managed-services-cross-subscription/main.tf index 91fb4620b4c1..60f6cde9a885 100644 --- a/examples/databricks/managed-services-cross-subscription/main.tf +++ b/examples/databricks/managed-services-cross-subscription/main.tf @@ -106,7 +106,7 @@ resource "azurerm_key_vault_access_policy" "managed" { key_vault_id = azurerm_key_vault.example.id tenant_id = azurerm_key_vault.example.tenant_id - object_id = "See the README.md file for instructions on how to lookup the correct value to enter here" + object_id = "00000000-0000-0000-0000-000000000000" # See the README.md file for instructions on how to lookup the correct value to enter here. key_permissions = [ "Get", diff --git a/examples/private-endpoint/databricks/managed-services/main.tf b/examples/private-endpoint/databricks/managed-services/main.tf index cacd09cf870f..0f4e22e9ac85 100644 --- a/examples/private-endpoint/databricks/managed-services/main.tf +++ b/examples/private-endpoint/databricks/managed-services/main.tf @@ -189,20 +189,22 @@ resource "azurerm_key_vault_access_policy" "terraform" { 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", + "Get", + "List", + "Create", + "Decrypt", + "Encrypt", + "Sign", + "UnwrapKey", + "Verify", + "WrapKey", + "Delete", + "Restore", + "Recover", + "Update", + "Purge", + "GetRotationPolicy", + "SetRotationPolicy", ] } @@ -214,21 +216,21 @@ resource "azurerm_key_vault_access_policy" "databricks" { object_id = azurerm_databricks_workspace.example.storage_account_identity.0.principal_id key_permissions = [ - "get", - "unwrapKey", - "wrapKey", + "Get", + "UnwrapKey", + "WrapKey", ] } resource "azurerm_key_vault_access_policy" "managed" { key_vault_id = azurerm_key_vault.example.id tenant_id = azurerm_key_vault.example.tenant_id - object_id = "See the README.md file for instructions on how to lookup the correct value to enter here" + object_id = "00000000-0000-0000-0000-000000000000" # See the README.md file for instructions on how to lookup the correct value to enter here. key_permissions = [ - "get", - "unwrapKey", - "wrapKey", + "Get", + "UnwrapKey", + "WrapKey", ] } From f92915abbb4b2784fadf50cca068d239c9173f48 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:04:12 -0700 Subject: [PATCH 14/28] Update code to allow all three keys to exist in different subscriptions... --- .../dbfs-cross-subscription/main.tf | 6 +- .../main.tf | 2 +- ...root_dbfs_customer_managed_key_resource.go | 63 +++++++++++-------- .../databricks_workspace_resource.go | 63 ++++++++++++++----- .../docs/r/databricks_workspace.html.markdown | 8 ++- ...ot_dbfs_customer_managed_key.html.markdown | 4 +- 6 files changed, 95 insertions(+), 51 deletions(-) diff --git a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf index 6c49053faf5a..07de8835b34e 100644 --- a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf +++ b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf @@ -42,9 +42,9 @@ resource "azurerm_databricks_workspace" "example" { resource "azurerm_databricks_workspace_root_dbfs_customer_managed_key" "example" { depends_on = [azurerm_key_vault_access_policy.databricks] - workspace_id = azurerm_databricks_workspace.example.id - key_vault_key_id = azurerm_key_vault_key.example.id - managed_cmk_key_vault_id = azurerm_key_vault.example.id + workspace_id = azurerm_databricks_workspace.example.id + key_vault_key_id = azurerm_key_vault_key.example.id + root_dbfs_cmk_key_vault_id = azurerm_key_vault.example.id } resource "azurerm_key_vault" "example" { diff --git a/examples/databricks/managed-services-cross-subscription/main.tf b/examples/databricks/managed-services-cross-subscription/main.tf index 60f6cde9a885..66a31ee9932f 100644 --- a/examples/databricks/managed-services-cross-subscription/main.tf +++ b/examples/databricks/managed-services-cross-subscription/main.tf @@ -34,7 +34,7 @@ resource "azurerm_databricks_workspace" "example" { sku = "premium" managed_resource_group_name = "${var.prefix}-DBW-managed-services" - managed_cmk_key_vault_id = azurerm_key_vault.example.id + managed_services_cmk_key_vault_id = azurerm_key_vault.example.id managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.example.id tags = { diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index 8f74c83d85f9..8b04d5268973 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -69,7 +69,7 @@ func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource }, // added to support cross subscription cmk's - "managed_cmk_key_vault_id": { + "root_dbfs_cmk_key_vault_id": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: commonids.ValidateKeyVaultID, @@ -129,24 +129,25 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // If the 'managed_cmk_key_vault_id' was not defined assume - // the key vault exists in the same subscription as the workspace... - resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + // If the 'root_dbfs_cmk_key_vault_id' was not defined assume the + // key vault exists in the same subscription as the workspace... + rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + + // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + rootDbfsKeyVaultId := d.Get("root_dbfs_cmk_key_vault_id").(string) - if managedKeyVaultId != "" { - // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedKeyVaultId) + if rootDbfsKeyVaultId != "" { + keyVaultId, err := commonids.ParseKeyVaultID(rootDbfsKeyVaultId) if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", rootDbfsKeyVaultId, err) } - resourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + rootDbfsSubscriptionId = commonids.NewSubscriptionID(keyVaultId.SubscriptionId) } // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) } @@ -182,7 +183,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa // Always set this even if it's empty to keep the state file // consistent with the configuration file... - d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + d.Set("root_dbfs_cmk_key_vault_id", rootDbfsKeyVaultId) return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } @@ -248,8 +249,13 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData } } - managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) - d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + var rootDbfsKeyVaultId string + if v, ok := d.GetOk("root_dbfs_cmk_key_vault_id"); ok { + rootDbfsKeyVaultId = v.(string) + } + d.Set("root_dbfs_cmk_key_vault_id", rootDbfsKeyVaultId) return nil } @@ -300,26 +306,27 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // If the 'managed_cmk_key_vault_id' was not defined assume + // If the 'root_dbfs_cmk_key_vault_id' was not defined assume // the key vault exists in the same subscription as the workspace... - resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + + // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + rootDbfsKeyVaultId := d.Get("root_dbfs_cmk_key_vault_id").(string) - if managedKeyVaultId != "" { - // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedKeyVaultId) + if rootDbfsKeyVaultId != "" { + v, err := commonids.ParseKeyVaultID(rootDbfsKeyVaultId) if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", rootDbfsKeyVaultId, err) } - resourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + rootDbfsSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) } // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", resourceSubscriptionId, key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", rootDbfsSubscriptionId, key.KeyVaultBaseUrl, err) } // We need to pull all of the custom params from the parent @@ -344,7 +351,9 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("updating Root DBFS Customer Managed Key for %s: %+v", *id, err) } - d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + d.Set("root_dbfs_cmk_key_vault_id", rootDbfsKeyVaultId) return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index ae22fd87cc19..482e45e886c2 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -89,7 +89,14 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { }, // added to support cross subscription cmk's - "managed_cmk_key_vault_id": { + "managed_services_cmk_key_vault_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + }, + + // added to support cross subscription cmk's + "managed_disk_cmk_key_vault_id": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: commonids.ValidateKeyVaultID, @@ -436,7 +443,8 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } requireNsgRules := d.Get("network_security_group_rules_required").(string) customParamsRaw := d.Get("custom_parameters").([]interface{}) - managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) + managedServicesKeyVaultId := d.Get("managed_services_cmk_key_vault_id").(string) + managedDiskKeyVaultId := d.Get("managed_disk_cmk_key_vault_id").(string) customParams, pubSubAssoc, priSubAssoc := expandWorkspaceCustomParameters(customParamsRaw, customerEncryptionEnabled, infrastructureEncryptionEnabled, backendPoolName, loadBalancerId) if len(customParamsRaw) > 0 && customParamsRaw[0] != nil { @@ -464,18 +472,20 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int encrypt.Entities = workspaces.EncryptionEntitiesDefinition{} servicesKeyIdRaw := d.Get("managed_services_cmk_key_vault_key_id").(string) - // if the 'managed_cmk_key_vault_id' was not defined assume - // the key vault exists in the same subscription as the workspace... - resourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - if managedKeyVaultId != "" { + // if the 'managed_services_cmk_key_vault_id'/'managed_disk_cmk_key_vault_id' was not defined + // assume the key vault exists in the same subscription as the workspace... + managedServicesResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + managedDiskResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + + if managedServicesKeyVaultId != "" { // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedKeyVaultId) + v, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedKeyVaultId, err) + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) } - resourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + managedServicesResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) } if servicesKeyIdRaw != "" { @@ -486,9 +496,9 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", resourceSubscriptionId, key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl, err) } encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ @@ -502,6 +512,17 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } } + if managedDiskKeyVaultId != "" { + // If they passed the 'managed_disk_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(managedDiskKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedDiskKeyVaultId, err) + } + + managedDiskResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + } + diskKeyIdRaw := d.Get("managed_disk_cmk_key_vault_key_id").(string) if diskKeyIdRaw != "" { setEncrypt = true @@ -511,9 +532,9 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourceSubscriptionId, key.KeyVaultBaseUrl) + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl) if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", resourceSubscriptionId, key.KeyVaultBaseUrl, err) + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl, err) } encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ @@ -591,7 +612,8 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // consistent with the configuration file... d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) - d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) return resourceDatabricksWorkspaceRead(d, meta) } @@ -742,8 +764,17 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} } d.Set("disk_encryption_set_id", encryptDiskEncryptionSetId) - managedKeyVaultId := d.Get("managed_cmk_key_vault_id").(string) - d.Set("managed_cmk_key_vault_id", managedKeyVaultId) + var managedServicesKeyVaultId string + if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { + managedServicesKeyVaultId = v.(string) + } + d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + + var managedDiskKeyVaultId string + if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { + managedDiskKeyVaultId = v.(string) + } + d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) return tags.FlattenAndSet(d, model.Tags) } diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 257a61b3ee34..7f50b459aa2f 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -48,9 +48,13 @@ 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_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` keys. +* `managed_services_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` key. -~> **Note:** The `managed_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_cmk_key_vault_id` field is defined in the configuration file both `managed_services_cmk_key_vault_key_id` and `managed_disk_cmk_key_vault_key_id` must be hosted in the same Key Vault resource which belongs to a different subscription than the Databricks Workspace. +~> **Note:** The `managed_services_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_services_cmk_key_vault_id` field is not specified it is assumed that the the `managed_services_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. + +* `managed_disk_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_disk_cmk_key_vault_key_id` key. + +~> **Note:** The `managed_disk_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_disk_cmk_key_vault_id` field is not specified it is assumed that the the `managed_disk_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. * `managed_services_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed resources(e.g. Notebooks and Artifacts). diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index bef178a71e5a..37ab62718bee 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -122,9 +122,9 @@ The following arguments are supported: * `key_vault_key_id` - (Required) The resource ID of the Key Vault Key to be used. -* `managed_cmk_key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. +* `root_dbfs_cmk_key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. -~> **Note:** The `managed_cmk_key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `managed_cmk_key_vault_id` field is not specified the current subscriptioin will be used. +~> **Note:** The `root_dbfs_cmk_key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `root_dbfs_cmk_key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. ## Attributes Reference From 2b28d5117239dc263d75a0fa69d5dd6e9ca9526d Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:37:05 -0700 Subject: [PATCH 15/28] Update field names to be more unified in the resources... --- .../dbfs-cross-subscription/main.tf | 8 +++--- .../main.tf | 27 +++++++++++++++++-- ...root_dbfs_customer_managed_key_resource.go | 14 +++++----- ...ot_dbfs_customer_managed_key.html.markdown | 4 +-- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf index 07de8835b34e..262a0085c92f 100644 --- a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf +++ b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf @@ -42,9 +42,9 @@ resource "azurerm_databricks_workspace" "example" { resource "azurerm_databricks_workspace_root_dbfs_customer_managed_key" "example" { depends_on = [azurerm_key_vault_access_policy.databricks] - workspace_id = azurerm_databricks_workspace.example.id - key_vault_key_id = azurerm_key_vault_key.example.id - root_dbfs_cmk_key_vault_id = azurerm_key_vault.example.id + workspace_id = azurerm_databricks_workspace.example.id + key_vault_id = azurerm_key_vault.example.id + key_vault_key_id = azurerm_key_vault_key.dbfs.id } resource "azurerm_key_vault" "example" { @@ -60,7 +60,7 @@ resource "azurerm_key_vault" "example" { soft_delete_retention_days = 7 } -resource "azurerm_key_vault_key" "example" { +resource "azurerm_key_vault_key" "dbfs" { depends_on = [azurerm_key_vault_access_policy.terraform] provider = azurerm.keyVaultSubscription diff --git a/examples/databricks/managed-services-cross-subscription/main.tf b/examples/databricks/managed-services-cross-subscription/main.tf index 66a31ee9932f..207536e9d6e0 100644 --- a/examples/databricks/managed-services-cross-subscription/main.tf +++ b/examples/databricks/managed-services-cross-subscription/main.tf @@ -35,8 +35,11 @@ resource "azurerm_databricks_workspace" "example" { managed_resource_group_name = "${var.prefix}-DBW-managed-services" managed_services_cmk_key_vault_id = azurerm_key_vault.example.id - managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.example.id + managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.services.id + managed_disk_cmk_key_vault_id = azurerm_key_vault.example.id + managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id + tags = { Environment = "Sandbox" } @@ -54,7 +57,27 @@ resource "azurerm_key_vault" "example" { soft_delete_retention_days = 7 } -resource "azurerm_key_vault_key" "example" { +resource "azurerm_key_vault_key" "services" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "${var.prefix}-certificate" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_key_vault_key" "disk" { depends_on = [azurerm_key_vault_access_policy.terraform] provider = azurerm.keyVaultSubscription diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index 8b04d5268973..f2b3222915ee 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -69,7 +69,7 @@ func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource }, // added to support cross subscription cmk's - "root_dbfs_cmk_key_vault_id": { + "key_vault_id": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: commonids.ValidateKeyVaultID, @@ -135,7 +135,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID // to extract the correct key vault subscription for the exists call... - rootDbfsKeyVaultId := d.Get("root_dbfs_cmk_key_vault_id").(string) + rootDbfsKeyVaultId := d.Get("key_vault_id").(string) if rootDbfsKeyVaultId != "" { keyVaultId, err := commonids.ParseKeyVaultID(rootDbfsKeyVaultId) @@ -183,7 +183,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa // Always set this even if it's empty to keep the state file // consistent with the configuration file... - d.Set("root_dbfs_cmk_key_vault_id", rootDbfsKeyVaultId) + d.Set("key_vault_id", rootDbfsKeyVaultId) return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } @@ -252,10 +252,10 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData // Always set this even if it's empty to keep the state file // consistent with the configuration file... var rootDbfsKeyVaultId string - if v, ok := d.GetOk("root_dbfs_cmk_key_vault_id"); ok { + if v, ok := d.GetOk("key_vault_id"); ok { rootDbfsKeyVaultId = v.(string) } - d.Set("root_dbfs_cmk_key_vault_id", rootDbfsKeyVaultId) + d.Set("key_vault_id", rootDbfsKeyVaultId) return nil } @@ -312,7 +312,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID // to extract the correct key vault subscription for the exists call... - rootDbfsKeyVaultId := d.Get("root_dbfs_cmk_key_vault_id").(string) + rootDbfsKeyVaultId := d.Get("key_vault_id").(string) if rootDbfsKeyVaultId != "" { v, err := commonids.ParseKeyVaultID(rootDbfsKeyVaultId) @@ -353,7 +353,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa // Always set this even if it's empty to keep the state file // consistent with the configuration file... - d.Set("root_dbfs_cmk_key_vault_id", rootDbfsKeyVaultId) + d.Set("key_vault_id", rootDbfsKeyVaultId) return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index 37ab62718bee..4e5e27bea175 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -122,9 +122,9 @@ The following arguments are supported: * `key_vault_key_id` - (Required) The resource ID of the Key Vault Key to be used. -* `root_dbfs_cmk_key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. +* `key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. -~> **Note:** The `root_dbfs_cmk_key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `root_dbfs_cmk_key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +~> **Note:** The `key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. ## Attributes Reference From 766adf1831a268a7e673407c1bc98fdd75dc6f5e Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:53:59 -0700 Subject: [PATCH 16/28] Fix lint error and add additional note to documentation... --- .../customer-managed-key/dbfs-cross-subscription/main.tf | 2 +- .../managed-services-cross-subscription/main.tf | 2 +- website/docs/r/databricks_workspace.html.markdown | 8 ++++++-- ...workspace_root_dbfs_customer_managed_key.html.markdown | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf index 262a0085c92f..ec484c55504a 100644 --- a/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf +++ b/examples/databricks/customer-managed-key/dbfs-cross-subscription/main.tf @@ -8,7 +8,7 @@ provider "azurerm" { provider "azurerm" { features {} alias = "keyVaultSubscription" - subscription_id = "{subscription where the Key Vault should be hosted}" + subscription_id = "00000000-0000-0000-0000-000000000000" # Subscription where the Key Vault should be hosted } data "azurerm_client_config" "current" {} diff --git a/examples/databricks/managed-services-cross-subscription/main.tf b/examples/databricks/managed-services-cross-subscription/main.tf index 207536e9d6e0..abad21c4c63a 100644 --- a/examples/databricks/managed-services-cross-subscription/main.tf +++ b/examples/databricks/managed-services-cross-subscription/main.tf @@ -8,7 +8,7 @@ provider "azurerm" { provider "azurerm" { features {} alias = "keyVaultSubscription" - subscription_id = "{subscription where the Key Vault should be hosted}" + subscription_id = "00000000-0000-0000-0000-000000000000" # Subscription where the Key Vault should be hosted } data "azurerm_client_config" "current" {} diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 7f50b459aa2f..5a57f77e2589 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -50,11 +50,15 @@ The following arguments are supported: * `managed_services_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` key. -~> **Note:** The `managed_services_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_services_cmk_key_vault_id` field is not specified it is assumed that the the `managed_services_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +-> **Note:** The `managed_services_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_services_cmk_key_vault_id` field is not specified it is assumed that the the `managed_services_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. + +-> **Note:** If you are using multiple service principals to execute Terraform across supscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. * `managed_disk_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_disk_cmk_key_vault_key_id` key. -~> **Note:** The `managed_disk_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_disk_cmk_key_vault_id` field is not specified it is assumed that the the `managed_disk_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +-> **Note:** The `managed_disk_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_disk_cmk_key_vault_id` field is not specified it is assumed that the the `managed_disk_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. + +-> **Note:** If you are using multiple service principals to execute Terraform across supscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. * `managed_services_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed resources(e.g. Notebooks and Artifacts). diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index 4e5e27bea175..3c4e32fb2365 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -124,7 +124,9 @@ The following arguments are supported: * `key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. -~> **Note:** The `key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +-> **Note:** The `key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. + +-> **Note:** If you are using multiple service principals to execute Terraform across supscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. ## Attributes Reference From 7215ce4d96dd56457e2087985fe9e02483bfaa60 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:57:46 -0700 Subject: [PATCH 17/28] Fix typo... --- website/docs/r/databricks_workspace.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 5a57f77e2589..4d5929011187 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -52,13 +52,13 @@ The following arguments are supported: -> **Note:** The `managed_services_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_services_cmk_key_vault_id` field is not specified it is assumed that the the `managed_services_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. --> **Note:** If you are using multiple service principals to execute Terraform across supscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. +-> **Note:** If you are using multiple service principals to execute Terraform across subscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. * `managed_disk_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_disk_cmk_key_vault_key_id` key. -> **Note:** The `managed_disk_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_disk_cmk_key_vault_id` field is not specified it is assumed that the the `managed_disk_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. --> **Note:** If you are using multiple service principals to execute Terraform across supscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. +-> **Note:** If you are using multiple service principals to execute Terraform across subscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. * `managed_services_cmk_key_vault_key_id` - (Optional) Customer managed encryption properties for the Databricks Workspace managed resources(e.g. Notebooks and Artifacts). From bb5ef7dd02f34360e364a393ce47a32eb20d1d3e Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:05:00 -0700 Subject: [PATCH 18/28] Missed one... --- ...ricks_workspace_root_dbfs_customer_managed_key.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index 3c4e32fb2365..d08077c8460b 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -126,7 +126,7 @@ The following arguments are supported: -> **Note:** The `key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. --> **Note:** If you are using multiple service principals to execute Terraform across supscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. +-> **Note:** If you are using multiple service principals to execute Terraform across subscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. ## Attributes Reference From 5437c666dc7d5ab14ee6c7d6b3d4c990ca1b154e Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 8 Mar 2024 23:47:06 -0700 Subject: [PATCH 19/28] Terraform fmt databricks directory... --- examples/databricks/managed-services-cross-subscription/main.tf | 2 +- ...ricks_workspace_root_dbfs_customer_managed_key.html.markdown | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/databricks/managed-services-cross-subscription/main.tf b/examples/databricks/managed-services-cross-subscription/main.tf index abad21c4c63a..d8aa23330351 100644 --- a/examples/databricks/managed-services-cross-subscription/main.tf +++ b/examples/databricks/managed-services-cross-subscription/main.tf @@ -39,7 +39,7 @@ resource "azurerm_databricks_workspace" "example" { managed_disk_cmk_key_vault_id = azurerm_key_vault.example.id managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id - + tags = { Environment = "Sandbox" } diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index d08077c8460b..d12414560eb9 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -113,6 +113,7 @@ resource "azurerm_key_vault_access_policy" "databricks" { * [Databricks Workspace with Root Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs) * [Databricks Workspace with Root Databricks File System Customer Managed Keys in a Different Subscription](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/databricks/customer-managed-key/dbfs-cross-subscription) +* [Databricks Workspace with Private Endpoint, Customer Managed Keys for Managed Services and Root Databricks File System Customer Managed Keys](https://github.com/hashicorp/terraform-provider-azurerm/tree/main/examples/private-endpoint/databricks/managed-services) ## Argument Reference From c271bbadfa6513f272d9e3265b98cc20e46752f1 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Sun, 10 Mar 2024 00:29:07 -0700 Subject: [PATCH 20/28] Add test cases... --- .../databricks_workspace_resource_test.go | 469 ++++++++++++++++++ 1 file changed, 469 insertions(+) diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index 02c4316ac155..206cc11f9325 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -6,6 +6,7 @@ package databricks_test import ( "context" "fmt" + "os" "strings" "testing" @@ -19,6 +20,10 @@ import ( type DatabricksWorkspaceResource struct{} +type DatabricksWorkspaceAlternateSubscription struct { + ID string +} + func TestAccDatabricksWorkspace_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") r := DatabricksWorkspaceResource{} @@ -291,6 +296,72 @@ func TestAccDatabricksWorkspace_managedServicesRootDbfsCMKAndPrivateLink(t *test }) } +func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { + altSubscription := altSubscriptionCheck() + + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") + } + + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + databricksPrincipalID := getDatabricksPrincipalId(data.Client().SubscriptionID) + r := DatabricksWorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.altSubscriptionCmkComplete(data, databricksPrincipalID, altSubscription.ID), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("managed_services_cmk_key_vault_id", "managed_disk_cmk_key_vault_id"), + }) +} + +func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { + altSubscription := altSubscriptionCheck() + + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") + } + + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + databricksPrincipalID := getDatabricksPrincipalId(data.Client().SubscriptionID) + r := DatabricksWorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.altSubscriptionCmkServicesOnly(data, databricksPrincipalID, altSubscription.ID), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("managed_services_cmk_key_vault_id"), + }) +} + +func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly(t *testing.T) { + altSubscription := altSubscriptionCheck() + + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") + } + + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + databricksPrincipalID := getDatabricksPrincipalId(data.Client().SubscriptionID) + r := DatabricksWorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.altSubscriptionCmkDiskOnly(data, databricksPrincipalID, altSubscription.ID), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("managed_disk_cmk_key_vault_id"), + }) +} + func getDatabricksPrincipalId(subscriptionId string) string { databricksPrincipalID := "bb9ef821-a78b-4312-90cc-5ece3fad3430" if strings.HasPrefix(strings.ToLower(subscriptionId), "85b3dbca") { @@ -300,6 +371,15 @@ func getDatabricksPrincipalId(subscriptionId string) string { return databricksPrincipalID } +func altSubscriptionCheck() *DatabricksWorkspaceAlternateSubscription { + altSubscriptonID := os.Getenv("ARM_SUBSCRIPTION_ID_ALT") + if altSubscriptonID == "" { + return nil + } + + return &DatabricksWorkspaceAlternateSubscription{ID: altSubscriptonID} +} + func (DatabricksWorkspaceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := workspaces.ParseWorkspaceID(state.ID) if err != nil { @@ -1837,3 +1917,392 @@ resource "azurerm_private_dns_cname_record" "test" { } `, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID) } + +func (DatabricksWorkspaceResource) altSubscriptionCmkComplete(data acceptance.TestData, databricksPrincipalID string, altKeyVaultSubscription string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +provider "azurerm" { + features {} + alias = "keyVaultSubscription" + subscription_id = "%[5]s" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-cross-subscription-complete-%[1]d" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVault" { + provider = azurerm.keyVaultSubscription + + name = "acctestRG-databricks-cross-subscription-complete-%[1]d" + location = "West Europe" +} + +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" + + managed_services_cmk_key_vault_id = azurerm_key_vault.test.id + managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.services.id + + managed_disk_cmk_key_vault_id = azurerm_key_vault.test.id + managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id + + tags = { + Environment = "Sandbox" + Pricing = "Premium" + } +} + +# Create this in a different subscription... +resource "azurerm_key_vault" "test" { + provider = azurerm.keyVaultSubscription + + name = "acctest-kv-%[3]s" + location = azurerm_resource_group.keyVault.location + resource_group_name = azurerm_resource_group.keyVault.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "services" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "acctest-services-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_key" "disk" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "acctest-disk-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" { + provider = azurerm.keyVaultSubscription + + 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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "managed" { + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_key_vault.test.tenant_id + object_id = "%[4]s" + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, altKeyVaultSubscription) +} + +func (DatabricksWorkspaceResource) altSubscriptionCmkServicesOnly(data acceptance.TestData, databricksPrincipalID string, altKeyVaultSubscription string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +provider "azurerm" { + features {} + alias = "keyVaultSubscription" + subscription_id = "%[5]s" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-cross-subscription-services-%[1]d" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVault" { + provider = azurerm.keyVaultSubscription + + name = "acctestRG-databricks-cross-subscription-services-%[1]d" + location = "West Europe" +} + +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" + + managed_services_cmk_key_vault_id = azurerm_key_vault.test.id + managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.services.id + + tags = { + Environment = "Sandbox" + Pricing = "Premium" + } +} + +# Create this in a different subscription... +resource "azurerm_key_vault" "test" { + provider = azurerm.keyVaultSubscription + + name = "acctest-kv-%[3]s" + location = azurerm_resource_group.keyVault.location + resource_group_name = azurerm_resource_group.keyVault.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "services" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "acctest-services-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" { + provider = azurerm.keyVaultSubscription + + 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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "managed" { + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_key_vault.test.tenant_id + object_id = "%[4]s" + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, altKeyVaultSubscription) +} + +func (DatabricksWorkspaceResource) altSubscriptionCmkDiskOnly(data acceptance.TestData, databricksPrincipalID string, altKeyVaultSubscription string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +provider "azurerm" { + features {} + alias = "keyVaultSubscription" + subscription_id = "%[5]s" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-cross-subscription-disk-%[1]d" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVault" { + provider = azurerm.keyVaultSubscription + + name = "acctestRG-databricks-cross-subscription-disk-%[1]d" + location = "West Europe" +} + +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" + + managed_disk_cmk_key_vault_id = azurerm_key_vault.test.id + managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id + + tags = { + Environment = "Sandbox" + Pricing = "Premium" + } +} + +# Create this in a different subscription... +resource "azurerm_key_vault" "test" { + provider = azurerm.keyVaultSubscription + + name = "acctest-kv-%[3]s" + location = azurerm_resource_group.keyVault.location + resource_group_name = azurerm_resource_group.keyVault.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "disk" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + provider = azurerm.keyVaultSubscription + + name = "acctest-disk-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" { + provider = azurerm.keyVaultSubscription + + 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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "managed" { + provider = azurerm.keyVaultSubscription + + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_key_vault.test.tenant_id + object_id = "%[4]s" + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, altKeyVaultSubscription) +} From b9db587717b6904ea5b82b3993763e82a0c3a4ce Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:54:07 -0600 Subject: [PATCH 21/28] Update altSubscriptionCheck function... --- .../databricks_workspace_resource_test.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index 206cc11f9325..6c82ae1e50ce 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -299,7 +299,7 @@ func TestAccDatabricksWorkspace_managedServicesRootDbfsCMKAndPrivateLink(t *test func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { altSubscription := altSubscriptionCheck() - if altSubscription == nil { + if altSubscription.ID == "" { t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") } @@ -321,7 +321,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { altSubscription := altSubscriptionCheck() - if altSubscription == nil { + if altSubscription.ID == "" { t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") } @@ -343,7 +343,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly(t *testing.T) { altSubscription := altSubscriptionCheck() - if altSubscription == nil { + if altSubscription.ID == "" { t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") } @@ -371,13 +371,10 @@ func getDatabricksPrincipalId(subscriptionId string) string { return databricksPrincipalID } -func altSubscriptionCheck() *DatabricksWorkspaceAlternateSubscription { +func altSubscriptionCheck() DatabricksWorkspaceAlternateSubscription { altSubscriptonID := os.Getenv("ARM_SUBSCRIPTION_ID_ALT") - if altSubscriptonID == "" { - return nil - } - return &DatabricksWorkspaceAlternateSubscription{ID: altSubscriptonID} + return DatabricksWorkspaceAlternateSubscription{ID: altSubscriptonID} } func (DatabricksWorkspaceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { From f2c08ca90468e562dd22b35a404464d258f90c0b Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:38:40 -0600 Subject: [PATCH 22/28] Update test cases... --- .../databricks_workspace_resource_test.go | 198 ++++++++++-------- 1 file changed, 109 insertions(+), 89 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index 6c82ae1e50ce..595383a4ccd8 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -21,7 +21,8 @@ import ( type DatabricksWorkspaceResource struct{} type DatabricksWorkspaceAlternateSubscription struct { - ID string + tenant_id string + subscription_id string } func TestAccDatabricksWorkspace_basic(t *testing.T) { @@ -299,8 +300,8 @@ func TestAccDatabricksWorkspace_managedServicesRootDbfsCMKAndPrivateLink(t *test func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { altSubscription := altSubscriptionCheck() - if altSubscription.ID == "" { - t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` and `ARM_TENANT_ID` environment variables to be specified") } data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") @@ -309,7 +310,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.altSubscriptionCmkComplete(data, databricksPrincipalID, altSubscription.ID), + Config: r.altSubscriptionCmkComplete(data, databricksPrincipalID, altSubscription), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -321,8 +322,8 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { altSubscription := altSubscriptionCheck() - if altSubscription.ID == "" { - t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` and `ARM_TENANT_ID` environment variables to be specified") } data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") @@ -331,7 +332,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.altSubscriptionCmkServicesOnly(data, databricksPrincipalID, altSubscription.ID), + Config: r.altSubscriptionCmkServicesOnly(data, databricksPrincipalID, altSubscription), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -343,8 +344,8 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly(t *testing.T) { altSubscription := altSubscriptionCheck() - if altSubscription.ID == "" { - t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` environment variable to be specified") + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` and `ARM_TENANT_ID` environment variables to be specified") } data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") @@ -353,7 +354,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.altSubscriptionCmkDiskOnly(data, databricksPrincipalID, altSubscription.ID), + Config: r.altSubscriptionCmkDiskOnly(data, databricksPrincipalID, altSubscription), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -371,10 +372,18 @@ func getDatabricksPrincipalId(subscriptionId string) string { return databricksPrincipalID } -func altSubscriptionCheck() DatabricksWorkspaceAlternateSubscription { +func altSubscriptionCheck() *DatabricksWorkspaceAlternateSubscription { altSubscriptonID := os.Getenv("ARM_SUBSCRIPTION_ID_ALT") + altTenantID := os.Getenv("ARM_TENANT_ID") - return DatabricksWorkspaceAlternateSubscription{ID: altSubscriptonID} + if altSubscriptonID == "" || altTenantID == "" { + return nil + } + + return &DatabricksWorkspaceAlternateSubscription{ + tenant_id: altTenantID, + subscription_id: altSubscriptonID, + } } func (DatabricksWorkspaceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { @@ -1915,45 +1924,50 @@ resource "azurerm_private_dns_cname_record" "test" { `, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID) } -func (DatabricksWorkspaceResource) altSubscriptionCmkComplete(data acceptance.TestData, databricksPrincipalID string, altKeyVaultSubscription string) string { +func (DatabricksWorkspaceResource) altSubscriptionCmkComplete(data acceptance.TestData, databricksPrincipalID string, alt *DatabricksWorkspaceAlternateSubscription) string { return fmt.Sprintf(` provider "azurerm" { - features {} + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } } -provider "azurerm" { +provider "azurerm-alt" { features {} - alias = "keyVaultSubscription" - subscription_id = "%[5]s" + + tenant_id = "%[5]s" + subscription_id = "%[6]s" } data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "test" { - name = "acctestRG-databricks-cross-subscription-complete-%[1]d" + name = "acctestRG-databricks-pri-sub-complete-%[1]d" location = "West Europe" } resource "azurerm_resource_group" "keyVault" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - name = "acctestRG-databricks-cross-subscription-complete-%[1]d" + name = "acctestRG-databricks-alt-sub-complete-%[1]d" location = "West Europe" } resource "azurerm_databricks_workspace" "test" { depends_on = [azurerm_key_vault_access_policy.managed] - name = "acctestDBW-%[1]d" + name = "acctest-databricks-pri-sub-%[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" + managed_resource_group_name = "databricks-pri-sub-managed-rg-%[1]d" - managed_services_cmk_key_vault_id = azurerm_key_vault.test.id + managed_services_cmk_key_vault_id = azurerm_key_vault.keyVault.id managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.services.id - managed_disk_cmk_key_vault_id = azurerm_key_vault.test.id + managed_disk_cmk_key_vault_id = azurerm_key_vault.keyVault.id managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id tags = { @@ -1963,12 +1977,12 @@ resource "azurerm_databricks_workspace" "test" { } # Create this in a different subscription... -resource "azurerm_key_vault" "test" { - provider = azurerm.keyVaultSubscription +resource "azurerm_key_vault" "keyVault" { + provider = azurerm-alt - name = "acctest-kv-%[3]s" - location = azurerm_resource_group.keyVault.location + name = "kv-altsub-%[3]s" resource_group_name = azurerm_resource_group.keyVault.name + location = azurerm_resource_group.keyVault.location tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" @@ -1976,12 +1990,11 @@ resource "azurerm_key_vault" "test" { } resource "azurerm_key_vault_key" "services" { + provider = azurerm-alt depends_on = [azurerm_key_vault_access_policy.terraform] - provider = azurerm.keyVaultSubscription - name = "acctest-services-certificate" - key_vault_id = azurerm_key_vault.test.id + key_vault_id = azurerm_key_vault.keyVault.id key_type = "RSA" key_size = 2048 @@ -1996,12 +2009,11 @@ resource "azurerm_key_vault_key" "services" { } resource "azurerm_key_vault_key" "disk" { + provider = azurerm-alt depends_on = [azurerm_key_vault_access_policy.terraform] - provider = azurerm.keyVaultSubscription - name = "acctest-disk-certificate" - key_vault_id = azurerm_key_vault.test.id + key_vault_id = azurerm_key_vault.keyVault.id key_type = "RSA" key_size = 2048 @@ -2016,10 +2028,10 @@ resource "azurerm_key_vault_key" "disk" { } resource "azurerm_key_vault_access_policy" "terraform" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - key_vault_id = azurerm_key_vault.test.id - tenant_id = azurerm_key_vault.test.tenant_id + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id object_id = data.azurerm_client_config.current.object_id key_permissions = [ @@ -2043,10 +2055,10 @@ resource "azurerm_key_vault_access_policy" "terraform" { } resource "azurerm_key_vault_access_policy" "managed" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - key_vault_id = azurerm_key_vault.test.id - tenant_id = azurerm_key_vault.test.tenant_id + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id object_id = "%[4]s" key_permissions = [ @@ -2057,45 +2069,50 @@ resource "azurerm_key_vault_access_policy" "managed" { "SetRotationPolicy", ] } -`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, altKeyVaultSubscription) +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, alt.tenant_id, alt.subscription_id) } -func (DatabricksWorkspaceResource) altSubscriptionCmkServicesOnly(data acceptance.TestData, databricksPrincipalID string, altKeyVaultSubscription string) string { +func (DatabricksWorkspaceResource) altSubscriptionCmkServicesOnly(data acceptance.TestData, databricksPrincipalID string, alt *DatabricksWorkspaceAlternateSubscription) string { return fmt.Sprintf(` provider "azurerm" { - features {} + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } } -provider "azurerm" { +provider "azurerm-alt" { features {} - alias = "keyVaultSubscription" - subscription_id = "%[5]s" + + tenant_id = "%[5]s" + subscription_id = "%[6]s" } data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "test" { - name = "acctestRG-databricks-cross-subscription-services-%[1]d" + name = "acctestRG-databricks-pri-sub-services-%[1]d" location = "West Europe" } resource "azurerm_resource_group" "keyVault" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - name = "acctestRG-databricks-cross-subscription-services-%[1]d" + name = "acctestRG-databricks-alt-sub-services-%[1]d" location = "West Europe" } resource "azurerm_databricks_workspace" "test" { depends_on = [azurerm_key_vault_access_policy.managed] - name = "acctestDBW-%[1]d" + name = "acctest-databricks-pri-sub-%[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" + managed_resource_group_name = "databricks-pri-sub-managed-rg-%[1]d" - managed_services_cmk_key_vault_id = azurerm_key_vault.test.id + managed_services_cmk_key_vault_id = azurerm_key_vault.keyVault.id managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.services.id tags = { @@ -2105,10 +2122,10 @@ resource "azurerm_databricks_workspace" "test" { } # Create this in a different subscription... -resource "azurerm_key_vault" "test" { - provider = azurerm.keyVaultSubscription +resource "azurerm_key_vault" "keyVault" { + provider = azurerm-alt - name = "acctest-kv-%[3]s" + name = "kv-altsub-%[3]s" location = azurerm_resource_group.keyVault.location resource_group_name = azurerm_resource_group.keyVault.name tenant_id = data.azurerm_client_config.current.tenant_id @@ -2118,12 +2135,11 @@ resource "azurerm_key_vault" "test" { } resource "azurerm_key_vault_key" "services" { + provider = azurerm-alt depends_on = [azurerm_key_vault_access_policy.terraform] - provider = azurerm.keyVaultSubscription - name = "acctest-services-certificate" - key_vault_id = azurerm_key_vault.test.id + key_vault_id = azurerm_key_vault.keyVault.id key_type = "RSA" key_size = 2048 @@ -2138,10 +2154,10 @@ resource "azurerm_key_vault_key" "services" { } resource "azurerm_key_vault_access_policy" "terraform" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - key_vault_id = azurerm_key_vault.test.id - tenant_id = azurerm_key_vault.test.tenant_id + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id object_id = data.azurerm_client_config.current.object_id key_permissions = [ @@ -2165,10 +2181,10 @@ resource "azurerm_key_vault_access_policy" "terraform" { } resource "azurerm_key_vault_access_policy" "managed" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - key_vault_id = azurerm_key_vault.test.id - tenant_id = azurerm_key_vault.test.tenant_id + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id object_id = "%[4]s" key_permissions = [ @@ -2179,45 +2195,50 @@ resource "azurerm_key_vault_access_policy" "managed" { "SetRotationPolicy", ] } -`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, altKeyVaultSubscription) +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, alt.tenant_id, alt.subscription_id) } -func (DatabricksWorkspaceResource) altSubscriptionCmkDiskOnly(data acceptance.TestData, databricksPrincipalID string, altKeyVaultSubscription string) string { +func (DatabricksWorkspaceResource) altSubscriptionCmkDiskOnly(data acceptance.TestData, databricksPrincipalID string, alt *DatabricksWorkspaceAlternateSubscription) string { return fmt.Sprintf(` provider "azurerm" { - features {} + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } } -provider "azurerm" { +provider "azurerm-alt" { features {} - alias = "keyVaultSubscription" - subscription_id = "%[5]s" + + tenant_id = "%[5]s" + subscription_id = "%[6]s" } data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "test" { - name = "acctestRG-databricks-cross-subscription-disk-%[1]d" + name = "acctestRG-databricks-pri-sub-disk-%[1]d" location = "West Europe" } resource "azurerm_resource_group" "keyVault" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - name = "acctestRG-databricks-cross-subscription-disk-%[1]d" + name = "acctestRG-databricks-alt-sub-disk-%[1]d" location = "West Europe" } resource "azurerm_databricks_workspace" "test" { depends_on = [azurerm_key_vault_access_policy.managed] - name = "acctestDBW-%[1]d" + name = "acctest-databricks-pri-sub-%[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" + managed_resource_group_name = "databricks-pri-sub-managed-rg-%[1]d" - managed_disk_cmk_key_vault_id = azurerm_key_vault.test.id + managed_disk_cmk_key_vault_id = azurerm_key_vault.keyVault.id managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id tags = { @@ -2227,10 +2248,10 @@ resource "azurerm_databricks_workspace" "test" { } # Create this in a different subscription... -resource "azurerm_key_vault" "test" { - provider = azurerm.keyVaultSubscription +resource "azurerm_key_vault" "keyVault" { + provider = azurerm-alt - name = "acctest-kv-%[3]s" + name = "kv-altsub-%[3]s" location = azurerm_resource_group.keyVault.location resource_group_name = azurerm_resource_group.keyVault.name tenant_id = data.azurerm_client_config.current.tenant_id @@ -2240,12 +2261,11 @@ resource "azurerm_key_vault" "test" { } resource "azurerm_key_vault_key" "disk" { + provider = azurerm-alt depends_on = [azurerm_key_vault_access_policy.terraform] - provider = azurerm.keyVaultSubscription - name = "acctest-disk-certificate" - key_vault_id = azurerm_key_vault.test.id + key_vault_id = azurerm_key_vault.keyVault.id key_type = "RSA" key_size = 2048 @@ -2260,10 +2280,10 @@ resource "azurerm_key_vault_key" "disk" { } resource "azurerm_key_vault_access_policy" "terraform" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - key_vault_id = azurerm_key_vault.test.id - tenant_id = azurerm_key_vault.test.tenant_id + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id object_id = data.azurerm_client_config.current.object_id key_permissions = [ @@ -2287,10 +2307,10 @@ resource "azurerm_key_vault_access_policy" "terraform" { } resource "azurerm_key_vault_access_policy" "managed" { - provider = azurerm.keyVaultSubscription + provider = azurerm-alt - key_vault_id = azurerm_key_vault.test.id - tenant_id = azurerm_key_vault.test.tenant_id + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id object_id = "%[4]s" key_permissions = [ @@ -2301,5 +2321,5 @@ resource "azurerm_key_vault_access_policy" "managed" { "SetRotationPolicy", ] } -`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, altKeyVaultSubscription) +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, alt.tenant_id, alt.subscription_id) } From e01be6fd64e8786031bdbe1ed8cbf4aed934b61c Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 14 Mar 2024 19:54:07 -0600 Subject: [PATCH 23/28] Replace the the with the... --- website/docs/r/databricks_workspace.html.markdown | 4 ++-- ...cks_workspace_root_dbfs_customer_managed_key.html.markdown | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/databricks_workspace.html.markdown b/website/docs/r/databricks_workspace.html.markdown index 4d5929011187..70c9197876b9 100644 --- a/website/docs/r/databricks_workspace.html.markdown +++ b/website/docs/r/databricks_workspace.html.markdown @@ -50,13 +50,13 @@ The following arguments are supported: * `managed_services_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_services_cmk_key_vault_key_id` key. --> **Note:** The `managed_services_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_services_cmk_key_vault_id` field is not specified it is assumed that the the `managed_services_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +-> **Note:** The `managed_services_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_services_cmk_key_vault_id` field is not specified it is assumed that the `managed_services_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. -> **Note:** If you are using multiple service principals to execute Terraform across subscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. * `managed_disk_cmk_key_vault_id` - (Optional) Resource ID of the Key Vault which contains the `managed_disk_cmk_key_vault_key_id` key. --> **Note:** The `managed_disk_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_disk_cmk_key_vault_id` field is not specified it is assumed that the the `managed_disk_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +-> **Note:** The `managed_disk_cmk_key_vault_id` field is only required if the Key Vault exists in a different subscription than the Databricks Workspace. If the `managed_disk_cmk_key_vault_id` field is not specified it is assumed that the `managed_disk_cmk_key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. -> **Note:** If you are using multiple service principals to execute Terraform across subscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. diff --git a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown index 3a0fea61ffb2..7f3a5ef39397 100644 --- a/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown +++ b/website/docs/r/databricks_workspace_root_dbfs_customer_managed_key.html.markdown @@ -125,7 +125,7 @@ The following arguments are supported: * `key_vault_id` - (Optional) Specifies the Resource ID of the Key Vault which contains the `key_vault_key_id`. --> **Note:** The `key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `key_vault_id` field is not specified it is assumed that the the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. +-> **Note:** The `key_vault_id` field only needs to be specified if the Key Vault which contains the `key_vault_key_id` exists in a different subscription than the Databricks Workspace. If the `key_vault_id` field is not specified it is assumed that the `key_vault_key_id` is hosted in the same subscriptioin as the Databricks Workspace. -> **Note:** If you are using multiple service principals to execute Terraform across subscriptions you will need to add an additional `azurerm_key_vault_access_policy` resource granting the service principal access to the key vault in that subscription. From 2da47cead53a689fca70846cfb42d93cbd79fd59 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 21 Mar 2024 01:04:06 -0600 Subject: [PATCH 24/28] Address PR comments, need to add 4.0 test cases... --- internal/features/four_point_oh.go | 8 +- ...root_dbfs_customer_managed_key_resource.go | 382 ++++++++++---- .../databricks_workspace_resource.go | 485 +++++++++++++----- .../databricks_workspace_resource_test.go | 247 ++++++++- 4 files changed, 875 insertions(+), 247 deletions(-) diff --git a/internal/features/four_point_oh.go b/internal/features/four_point_oh.go index 423a527f657b..f6e7927451ad 100644 --- a/internal/features/four_point_oh.go +++ b/internal/features/four_point_oh.go @@ -3,6 +3,8 @@ package features +import "os" + // nolint gocritic // DeprecatedInFourPointOh returns the deprecation message if the provider // is running in 4.0 mode - otherwise is returns an empty string (such that @@ -24,7 +26,11 @@ func DeprecatedInFourPointOh(deprecationMessage string) string { // This exists to allow breaking changes to be piped through the provider // during the development of 3.x until 4.0 is ready. func FourPointOh() bool { - return false + // WodansSon: Added for testing 4.0 functionality, + // will comment out in final check-in... + return !(os.Getenv("TF_FOUR_POINT_OH_BETA") == "") + + // return false } // FourPointOhBeta returns whether this provider is running in 4.0 mode diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index f2b3222915ee..0aa14bbe1668 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -16,15 +16,17 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/databricks/2023-02-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/locks" keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" ) func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource { - return &pluginsdk.Resource{ + resource := &pluginsdk.Resource{ Create: databricksWorkspaceRootDbfsCustomerManagedKeyCreate, Read: databricksWorkspaceRootDbfsCustomerManagedKeyRead, Update: databricksWorkspaceRootDbfsCustomerManagedKeyUpdate, @@ -60,22 +62,36 @@ func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource Required: true, ValidateFunc: workspaces.ValidateWorkspaceID, }, + }, + } - // Make this key vault key id and abstract everything from the string... - "key_vault_key_id": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: keyVaultValidate.KeyVaultChildID, - }, + if !features.FourPointOhBeta() { + // NOTE: Added to support cross subscription cmk's in 3.x and to migrate the resource from + // using the keys data plane URL as the fields value to using the keys resource id + // instead in 4.0... + resource.Schema["key_vault_key_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + Deprecated: "`key_vault_key_id` will be removed in favour of the property `key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", + } - // added to support cross subscription cmk's - "key_vault_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: commonids.ValidateKeyVaultID, - }, - }, + resource.Schema["key_vault_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + Deprecated: "`key_vault_id` will be removed in favour of the property `key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", + } + } else { + // NOTE: This field maybe versioned or versionless... + resource.Schema["key_vault_key_resource_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), + } } + + return resource } func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceData, meta interface{}) error { @@ -89,10 +105,35 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return err } - keyIdRaw := d.Get("key_vault_key_id").(string) - key, err := keyVaultParse.ParseNestedItemID(keyIdRaw) - if err != nil { - return err + var keyIdRaw string // TODO: Remove in 4.0 + var dbfsKeyVaultId string // TODO: Remove in 4.0 + var key *keyVaultParse.NestedItemId // TODO: Remove in 4.0 + var keyVaultKeyId string + var dbfsKey *commonids.KeyVaultKeyVersionId + var dbfsKeyId string + + if !features.FourPointOhBeta() { + if v, ok := d.GetOk("key_vault_key_id"); ok { + keyIdRaw = v.(string) + } + + if v, ok := d.GetOk("key_vault_id"); ok { + dbfsKeyVaultId = v.(string) + } + + key, err = keyVaultParse.ParseNestedItemID(keyIdRaw) + if err != nil { + return err + } + } else { + if v, ok := d.GetOk("key_vault_key_resource_id"); ok { + keyVaultKeyId = v.(string) + } + + dbfsKey, err = parseWorkspaceManagedCmkKeyWithOptionalVersion(keyVaultKeyId) + if err != nil { + return err + } } // Not sure if I should also lock the key vault here too @@ -129,47 +170,87 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // If the 'root_dbfs_cmk_key_vault_id' was not defined assume the - // key vault exists in the same subscription as the workspace... - rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + if !features.FourPointOhBeta() { + // If the 'root_dbfs_cmk_key_vault_id' was not defined assume the + // key vault exists in the same subscription as the workspace... + rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - rootDbfsKeyVaultId := d.Get("key_vault_id").(string) + // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + if dbfsKeyVaultId != "" { + keyVaultId, err := commonids.ParseKeyVaultID(dbfsKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", dbfsKeyVaultId, err) + } - if rootDbfsKeyVaultId != "" { - keyVaultId, err := commonids.ParseKeyVaultID(rootDbfsKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", rootDbfsKeyVaultId, err) + rootDbfsSubscriptionId = commonids.NewSubscriptionID(keyVaultId.SubscriptionId) } - rootDbfsSubscriptionId = commonids.NewSubscriptionID(keyVaultId.SubscriptionId) - } + // make sure the key vault exists + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) + if err != nil || keyVaultIdRaw == nil { + return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) + } - // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) - if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) - } + // Only throw the import error if the keysource value has been set to something other than default... + if params.Encryption != nil && params.Encryption.Value != nil && keySource != workspaces.KeySourceDefault { + return tf.ImportAsExistsError("azurerm_databricks_workspace_root_dbfs_customer_managed_key", id.ID()) + } - // Only throw the import error if the keysource value has been set to something other than default... - if params.Encryption != nil && params.Encryption.Value != nil && keySource != workspaces.KeySourceDefault { - return tf.ImportAsExistsError("azurerm_databricks_workspace_root_dbfs_customer_managed_key", id.ID()) - } + // We need to pull all of the custom params from the parent + // workspace resource and then add our new encryption values into the + // structure, else the other values set in the parent workspace + // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ + // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' + // fields have a default value in the parent workspace resource. + params.Encryption = &workspaces.WorkspaceEncryptionParameter{ + Value: &workspaces.Encryption{ + KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), + KeyName: pointer.To(key.Name), + Keyversion: pointer.To(key.Version), + Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), + }, + } + } else { + if dbfsKey != nil { + // Make sure the key vault exists + keyVaultId := commonids.NewKeyVaultID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName) + resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the Root DBFS Customer Managed %s: %+v", keyVaultId, err) + } - // We need to pull all of the custom params from the parent - // workspace resource and then add our new encryption values into the - // structure, else the other values set in the parent workspace - // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ - // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' - // fields have a default value in the parent workspace resource. - params.Encryption = &workspaces.WorkspaceEncryptionParameter{ - Value: &workspaces.Encryption{ - KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), - KeyName: pointer.To(key.Name), - Keyversion: pointer.To(key.Version), - Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), - }, + keyVaultModel := resp.Model + if keyVaultModel == nil { + return fmt.Errorf("retrieving %s: model was nil", keyVaultId) + } + + // Only throw the import error if the keysource value has been set to something other than default... + if params.Encryption != nil && params.Encryption.Value != nil && keySource != workspaces.KeySourceDefault { + return tf.ImportAsExistsError("azurerm_databricks_workspace_root_dbfs_customer_managed_key", id.ID()) + } + + // We need to pull all of the custom params from the parent + // workspace resource and then add our new encryption values into the + // structure, else the other values set in the parent workspace + // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ + // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' + // fields have a default value in the parent workspace resource. + params.Encryption = &workspaces.WorkspaceEncryptionParameter{ + Value: &workspaces.Encryption{ + KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), + KeyName: pointer.To(dbfsKey.KeyName), + Keyvaulturi: keyVaultModel.Properties.VaultUri, + }, + } + + if dbfsKey.VersionName == "" { + dbfsKeyId = commonids.NewKeyVaultKeyID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName, dbfsKey.KeyName).ID() + } else { + params.Encryption.Value.Keyversion = pointer.To(dbfsKey.VersionName) + dbfsKeyId = dbfsKey.ID() + } + } } props := pointer.From(workspace.Model) @@ -183,7 +264,11 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa // Always set this even if it's empty to keep the state file // consistent with the configuration file... - d.Set("key_vault_id", rootDbfsKeyVaultId) + if !features.FourPointOhBeta() { + d.Set("key_vault_id", dbfsKeyVaultId) + } else { + d.Set("key_vault_key_resource_id", dbfsKeyId) + } return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } @@ -209,10 +294,13 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData return fmt.Errorf("retrieving %s: %+v", *id, err) } - keySource := "" - keyName := "" - keyVersion := "" - keyVaultURI := "" + var keySource string + var keyName string + var keyVersion string + var keyVaultURI string + var dbfsKeyVaultId string // TODO: Remove in 4.0 + var dbfsKeyVaultKeyId string + var dbfsKey string if model := resp.Model; model != nil { if model.Properties.Parameters != nil { @@ -242,20 +330,39 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData d.SetId(id.ID()) d.Set("workspace_id", id.ID()) - if keyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(keyVaultURI, keyVaultParse.NestedItemTypeKey, keyName, keyVersion) - if err == nil { - d.Set("key_vault_key_id", key.ID()) + if !features.FourPointOhBeta() { + if keyVaultURI != "" { + key, err := keyVaultParse.NewNestedItemID(keyVaultURI, keyVaultParse.NestedItemTypeKey, keyName, keyVersion) + if err == nil { + d.Set("key_vault_key_id", key.ID()) + } + } + + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + if v, ok := d.GetOk("key_vault_id"); ok { + dbfsKeyVaultId = v.(string) + } + d.Set("key_vault_id", dbfsKeyVaultId) + } else { + // I have to pull this from state because there is no where + // to grab the subscription value from... + if v, ok := d.GetOk("key_vault_key_resource_id"); ok { + dbfsKey = v.(string) } - } - // Always set this even if it's empty to keep the state file - // consistent with the configuration file... - var rootDbfsKeyVaultId string - if v, ok := d.GetOk("key_vault_id"); ok { - rootDbfsKeyVaultId = v.(string) + if dbfsKey != "" { + key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(dbfsKey) + if err == nil { + if key.VersionName == "" { + dbfsKeyVaultKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() + } else { + dbfsKeyVaultKeyId = key.ID() + } + } + } + d.Set("key_vault_key_resource_id", dbfsKeyVaultKeyId) } - d.Set("key_vault_id", rootDbfsKeyVaultId) return nil } @@ -271,10 +378,31 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return err } - keyIdRaw := d.Get("key_vault_key_id").(string) - key, err := keyVaultParse.ParseNestedItemID(keyIdRaw) - if err != nil { - return err + var key *keyVaultParse.NestedItemId // TODO: Remove in 4.0 + var dbfsKeyVaultId string // TODO: Remove in 4.0 + + var params *workspaces.WorkspaceCustomParameters + var dbfsKey *commonids.KeyVaultKeyVersionId + var dbfsKeyId string + + keyVaultKeyId := d.Get("key_vault_key_resource_id").(string) + + if !features.FourPointOhBeta() { + var keyVaultKeyIdRaw string + + if v, ok := d.GetOk("key_vault_key_id"); ok { + keyVaultKeyIdRaw = v.(string) + } + + key, err = keyVaultParse.ParseNestedItemID(keyVaultKeyIdRaw) + if err != nil { + return err + } + } else { + dbfsKey, err = parseWorkspaceManagedCmkKeyWithOptionalVersion(keyVaultKeyId) + if err != nil { + return err + } } // Not sure if I should also lock the key vault here too @@ -288,8 +416,6 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("retrieving %s: %+v", *id, err) } - var params *workspaces.WorkspaceCustomParameters - if model := workspace.Model; model != nil { if params = model.Properties.Parameters; params != nil { if params.PrepareEncryption != nil { @@ -306,42 +432,80 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // If the 'root_dbfs_cmk_key_vault_id' was not defined assume - // the key vault exists in the same subscription as the workspace... - rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + // TODO: Remove in 4.0 + if v, ok := d.GetOk("key_vault_id"); ok { + dbfsKeyVaultId = v.(string) + } - // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - rootDbfsKeyVaultId := d.Get("key_vault_id").(string) + if !features.FourPointOhBeta() { + // If the 'root_dbfs_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... + rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - if rootDbfsKeyVaultId != "" { - v, err := commonids.ParseKeyVaultID(rootDbfsKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", rootDbfsKeyVaultId, err) + if dbfsKeyVaultId != "" { + v, err := commonids.ParseKeyVaultID(dbfsKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", dbfsKeyVaultId, err) + } + + rootDbfsSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) } - rootDbfsSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) - } + // make sure the key vault exists + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) + if err != nil || keyVaultIdRaw == nil { + return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", rootDbfsSubscriptionId, key.KeyVaultBaseUrl, err) + } - // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) - if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", rootDbfsSubscriptionId, key.KeyVaultBaseUrl, err) - } + // We need to pull all of the custom params from the parent + // workspace resource and then add our new encryption values into the + // structure, else the other values set in the parent workspace + // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ + // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' + // fields have a default value in the parent workspace resource. + params.Encryption = &workspaces.WorkspaceEncryptionParameter{ + Value: &workspaces.Encryption{ + KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), + KeyName: pointer.To(key.Name), + Keyversion: pointer.To(key.Version), + Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), + }, + } + } else { + if dbfsKey != nil { + // Make sure the key vault exists + keyVaultId := commonids.NewKeyVaultID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName) + resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the Root DBFS Customer Managed %s: %+v", keyVaultId, err) + } - // We need to pull all of the custom params from the parent - // workspace resource and then add our new encryption values into the - // structure, else the other values set in the parent workspace - // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ - // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' - // fields have a default value in the parent workspace resource. - params.Encryption = &workspaces.WorkspaceEncryptionParameter{ - Value: &workspaces.Encryption{ - KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), - KeyName: pointer.To(key.Name), - Keyversion: pointer.To(key.Version), - Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), - }, + keyVaultModel := resp.Model + if keyVaultModel == nil { + return fmt.Errorf("retrieving %s: model was nil", keyVaultId) + } + + // We need to pull all of the custom params from the parent + // workspace resource and then add our new encryption values into the + // structure, else the other values set in the parent workspace + // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ + // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' + // fields have a default value in the parent workspace resource. + params.Encryption = &workspaces.WorkspaceEncryptionParameter{ + Value: &workspaces.Encryption{ + KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), + KeyName: pointer.To(dbfsKey.KeyName), + Keyvaulturi: keyVaultModel.Properties.VaultUri, + }, + } + + if dbfsKey.VersionName != "" { + params.Encryption.Value.Keyversion = pointer.To(dbfsKey.VersionName) + dbfsKeyId = dbfsKey.ID() + } else { + dbfsKeyId = commonids.NewKeyVaultKeyID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName, dbfsKey.KeyName).ID() + } + } } props := pointer.From(workspace.Model) @@ -351,9 +515,13 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("updating Root DBFS Customer Managed Key for %s: %+v", *id, err) } - // Always set this even if it's empty to keep the state file - // consistent with the configuration file... - d.Set("key_vault_id", rootDbfsKeyVaultId) + if !features.FourPointOhBeta() { + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + d.Set("key_vault_id", dbfsKeyVaultId) + } else { + d.Set("key_vault_key_resource_id", dbfsKeyId) + } return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 5ed77cf447a7..9e4da222ef88 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/locks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/databricks/validate" keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" @@ -34,7 +35,7 @@ import ( ) func resourceDatabricksWorkspace() *pluginsdk.Resource { - return &pluginsdk.Resource{ + resource := &pluginsdk.Resource{ Create: resourceDatabricksWorkspaceCreateUpdate, Read: resourceDatabricksWorkspaceRead, Update: resourceDatabricksWorkspaceCreateUpdate, @@ -88,32 +89,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { Default: false, }, - // added to support cross subscription cmk's - "managed_services_cmk_key_vault_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: commonids.ValidateKeyVaultID, - }, - - // added to support cross subscription cmk's - "managed_disk_cmk_key_vault_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: commonids.ValidateKeyVaultID, - }, - - "managed_services_cmk_key_vault_key_id": { - Type: pluginsdk.TypeString, - Optional: 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, @@ -367,6 +342,54 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { return nil }), } + + if !features.FourPointOhBeta() { + // NOTE: Added to support cross subscription cmk's in 3.x and to migrate the resource from + // using the keys data plane URL as the fields value to using the keys resource id + // instead in 4.0... + resource.Schema["managed_services_cmk_key_vault_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + Deprecated: "`managed_services_cmk_key_vault_id` will be removed in favour of the property `managed_services_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", + } + + resource.Schema["managed_services_cmk_key_vault_key_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + Deprecated: "`managed_services_cmk_key_vault_key_id` will be removed in favour of the property `managed_services_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", + } + + resource.Schema["managed_disk_cmk_key_vault_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + Deprecated: "`managed_disk_cmk_key_vault_id` will be removed in favour of the property `managed_disk_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", + } + + resource.Schema["managed_disk_cmk_key_vault_key_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + Deprecated: "`managed_disk_cmk_key_vault_key_id` will be removed in favour of the property `managed_disk_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", + } + } else { + // NOTE: These fields maybe versioned or versionless... + resource.Schema["managed_services_cmk_key_vault_key_resource_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), + } + + resource.Schema["managed_disk_cmk_key_vault_key_resource_id"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), + } + } + + return resource } func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { @@ -443,8 +466,6 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int } requireNsgRules := d.Get("network_security_group_rules_required").(string) customParamsRaw := d.Get("custom_parameters").([]interface{}) - managedServicesKeyVaultId := d.Get("managed_services_cmk_key_vault_id").(string) - managedDiskKeyVaultId := d.Get("managed_disk_cmk_key_vault_id").(string) customParams, pubSubAssoc, priSubAssoc := expandWorkspaceCustomParameters(customParamsRaw, customerEncryptionEnabled, infrastructureEncryptionEnabled, backendPoolName, loadBalancerId) if len(customParamsRaw) > 0 && customParamsRaw[0] != nil { @@ -470,89 +491,196 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int setEncrypt := false encrypt := &workspaces.WorkspacePropertiesEncryption{} encrypt.Entities = workspaces.EncryptionEntitiesDefinition{} - servicesKeyIdRaw := d.Get("managed_services_cmk_key_vault_key_id").(string) - // if the 'managed_services_cmk_key_vault_id'/'managed_disk_cmk_key_vault_id' was not defined - // assume the key vault exists in the same subscription as the workspace... - managedServicesResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - managedDiskResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + // TODO: Remove in 4.0 + var managedServicesKeyIdRaw string + var managedServicesKeyVaultId string + var managedDiskKeyIdRaw string + var managedDiskKeyVaultId string + + // NOTE: Keep in 4.0 + var serviceKeyIdRaw string + var diskKeyIdRaw string + var servicesKeyId string + var diskKeyId string + + if !features.FourPointOhBeta() { + // set default subscription as current subscription for key vault look-up... + managedServicesResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + managedDiskResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + + if v, ok := d.GetOk("managed_services_cmk_key_vault_key_id"); ok { + managedServicesKeyIdRaw = v.(string) + } - if managedServicesKeyVaultId != "" { - // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) + if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { + managedServicesKeyVaultId = v.(string) } - managedServicesResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) - } + if v, ok := d.GetOk("managed_disk_cmk_key_vault_key_id"); ok { + managedDiskKeyIdRaw = v.(string) + } - if servicesKeyIdRaw != "" { - setEncrypt = true - key, err := keyVaultParse.ParseNestedItemID(servicesKeyIdRaw) - if err != nil { - return err + if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { + managedDiskKeyVaultId = v.(string) } - // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl) - if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl, err) + if managedServicesKeyVaultId != "" { + // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) + } + + managedServicesResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) } - 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, - }, + if managedServicesKeyIdRaw != "" { + setEncrypt = true + key, err := keyVaultParse.ParseNestedItemID(managedServicesKeyIdRaw) + if err != nil { + return err + } + + // make sure the key vault exists + _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl, err) + } + + encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ + KeyName: key.Name, + KeyVersion: key.Version, + KeyVaultUri: key.KeyVaultBaseUrl, + }, + } } - } - if managedDiskKeyVaultId != "" { - // If they passed the 'managed_disk_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedDiskKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedDiskKeyVaultId, err) + if managedDiskKeyVaultId != "" { + // If they passed the 'managed_disk_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(managedDiskKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedDiskKeyVaultId, err) + } + + managedDiskResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) } - managedDiskResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) - } + if managedDiskKeyIdRaw != "" { + setEncrypt = true + key, err := keyVaultParse.ParseNestedItemID(managedDiskKeyIdRaw) + if err != nil { + return err + } - 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 + _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl, err) + } + + encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: workspaces.ManagedDiskEncryptionKeyVaultProperties{ + KeyName: key.Name, + KeyVersion: key.Version, + KeyVaultUri: key.KeyVaultBaseUrl, + }, + } + } + } else { + // Migrate to new 4.0 resource ID fields... + if v, ok := d.GetOk("managed_services_cmk_key_vault_key_resource_id"); ok { + serviceKeyIdRaw = v.(string) } - // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl) - if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl, err) + if v, ok := d.GetOk("managed_disk_cmk_key_vault_key_resource_id"); ok { + diskKeyIdRaw = v.(string) } - 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, - }, + if serviceKeyIdRaw != "" { + setEncrypt = true + + // NOTE: The key ID may or may not be versionless... + key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(serviceKeyIdRaw) + if err != nil { + return err + } + + // Make sure the key vault exists + keyVaultId := commonids.NewKeyVaultID(key.SubscriptionId, key.ResourceGroupName, key.VaultName) + resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault %q in subscription %q: %+v", keyVaultId, key.SubscriptionId, err) + } + + model := resp.Model + if model == nil { + return fmt.Errorf("retrieving %s: model was nil", keyVaultId) + } + + encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ + KeyName: key.KeyName, + KeyVaultUri: *model.Properties.VaultUri, + }, + } + + if key.VersionName == "" { + servicesKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() + } else { + encrypt.Entities.ManagedServices.KeyVaultProperties.KeyVersion = key.VersionName + servicesKeyId = key.ID() + } } - rotationEnabled := d.Get("managed_disk_cmk_rotation_to_latest_version_enabled").(bool) - if rotationEnabled { - encrypt.Entities.ManagedDisk.RotationToLatestKeyVersionEnabled = utils.Bool(rotationEnabled) + if diskKeyIdRaw != "" { + setEncrypt = true + + // NOTE: The key ID may or may not be versionless... + key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(diskKeyIdRaw) + if err != nil { + return err + } + + // Make sure the key vault exists + keyVaultId := commonids.NewKeyVaultID(key.SubscriptionId, key.ResourceGroupName, key.VaultName) + resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault %q in subscription %q: %+v", keyVaultId, key.SubscriptionId, err) + } + + model := resp.Model + if model == nil { + return fmt.Errorf("retrieving %s: model was nil", keyVaultId) + } + + encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: workspaces.ManagedDiskEncryptionKeyVaultProperties{ + KeyName: key.KeyName, + KeyVaultUri: *model.Properties.VaultUri, + }, + } + + if key.VersionName == "" { + diskKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() + } else { + encrypt.Entities.ManagedDisk.KeyVaultProperties.KeyVersion = key.VersionName + diskKeyId = key.ID() + } } } + if rotationEnabled := d.Get("managed_disk_cmk_rotation_to_latest_version_enabled").(bool); rotationEnabled { + encrypt.Entities.ManagedDisk.RotationToLatestKeyVersionEnabled = utils.Bool(rotationEnabled) + } + // Including the Tags in the workspace parameters will update the tags on // the workspace only workspace := workspaces.Workspace{ @@ -610,10 +738,15 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // Always set these even if they are empty to keep the state file // consistent with the configuration file... - d.Set("managed_services_cmk_key_vault_key_id", servicesKeyIdRaw) - d.Set("managed_disk_cmk_key_vault_key_id", diskKeyIdRaw) - d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) - d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) + if !features.FourPointOhBeta() { + d.Set("managed_services_cmk_key_vault_key_id", managedServicesKeyIdRaw) + d.Set("managed_disk_cmk_key_vault_key_id", managedDiskKeyIdRaw) + d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) + } else { + d.Set("managed_services_cmk_key_vault_key_resource_id", servicesKeyId) + d.Set("managed_disk_cmk_key_vault_key_resource_id", diskKeyId) + } return resourceDatabricksWorkspaceRead(d, meta) } @@ -711,75 +844,155 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} } d.Set("workspace_id", workspaceId) - // customer managed key for managed services - var encryptKeyName string - var encryptKeyVersion string - var encryptKeyVaultURI string - var managedServicesKeyId string + var encryptDiskRotationEnabled bool - if encryption := model.Properties.Encryption; encryption != nil { - if encryptionProps := encryption.Entities.ManagedServices; encryptionProps != nil { - encryptKeyName = encryptionProps.KeyVaultProperties.KeyName - encryptKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion - encryptKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri + if !features.FourPointOhBeta() { + var managedServicesKeyName string + var managedServicesKeyVersion string + var managedServicesKeyVaultURI string + var managedServicesKeyId string + var managedDiskKeyName string + var managedDiskKeyVersion string + var managedDiskKeyVaultURI string + var managedDiskKeyId string + + // customer managed key for managed services + if encryption := model.Properties.Encryption; encryption != nil { + if encryptionProps := encryption.Entities.ManagedServices; encryptionProps != nil { + managedServicesKeyName = encryptionProps.KeyVaultProperties.KeyName + managedServicesKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion + managedServicesKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri + + if managedServicesKeyVaultURI != "" { + key, err := keyVaultParse.NewNestedItemID(managedServicesKeyVaultURI, keyVaultParse.NestedItemTypeKey, managedServicesKeyName, managedServicesKeyVersion) + if err == nil { + managedServicesKeyId = key.ID() + } + } + } + } + d.Set("managed_services_cmk_key_vault_key_id", managedServicesKeyId) + + // customer managed key for managed disk + if encryption := model.Properties.Encryption; encryption != nil { + if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { + managedDiskKeyName = encryptionProps.KeyVaultProperties.KeyName + managedDiskKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion + managedDiskKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri + encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled + } - if encryptKeyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(encryptKeyVaultURI, keyVaultParse.NestedItemTypeKey, encryptKeyName, encryptKeyVersion) + if managedDiskKeyVaultURI != "" { + key, err := keyVaultParse.NewNestedItemID(managedDiskKeyVaultURI, keyVaultParse.NestedItemTypeKey, managedDiskKeyName, managedDiskKeyVersion) if err == nil { - managedServicesKeyId = key.ID() + managedDiskKeyId = key.ID() } } } - } - d.Set("managed_services_cmk_key_vault_key_id", managedServicesKeyId) + d.Set("managed_disk_cmk_key_vault_key_id", managedDiskKeyId) - // customer managed key for managed disk - var encryptDiskKeyName string - var encryptDiskKeyVersion string - var encryptDiskKeyVaultURI string - var encryptDiskEncryptionSetId string - var managedDiskKeyId string - var encryptDiskRotationEnabled bool + var managedServicesKeyVaultId string + if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { + managedServicesKeyVaultId = v.(string) + } + d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) - 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 + var managedDiskKeyVaultId string + if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { + managedDiskKeyVaultId = v.(string) } + d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) + } else { + // Set new 4.0 values... + var servicesKeyIdRaw string + var diskKeyIdRaw string + var servicesKeyId string + var diskKeyId string + + // NOTE: I have to pull this from state else I won't know what subscription + // to use in the call to the NewKeyVaultKeyID/NewKeyVaultKeyVersionID function... + if v := d.Get("managed_services_cmk_key_vault_key_resource_id"); v != nil { + servicesKeyIdRaw = v.(string) + } + + if v := d.Get("managed_disk_cmk_key_vault_key_resource_id"); v != nil { + diskKeyIdRaw = v.(string) + } + + // customer managed key for managed services + if servicesKeyIdRaw != "" { + key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(servicesKeyIdRaw) + if err == nil { + if key.VersionName == "" { + servicesKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() + } else { + servicesKeyId = key.ID() + } + } + } + d.Set("managed_services_cmk_key_vault_key_resource_id", servicesKeyId) - if encryptDiskKeyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(encryptDiskKeyVaultURI, keyVaultParse.NestedItemTypeKey, encryptDiskKeyName, encryptDiskKeyVersion) + // customer managed key for managed disk + if diskKeyIdRaw != "" { + key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(diskKeyIdRaw) if err == nil { - managedDiskKeyId = key.ID() + if key.VersionName == "" { + diskKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() + } else { + diskKeyId = key.ID() + } + } + + if encryption := model.Properties.Encryption; encryption != nil { + if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { + encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled + } } } + d.Set("managed_disk_cmk_key_vault_key_resource_id", diskKeyId) } + d.Set("managed_disk_cmk_rotation_to_latest_version_enabled", encryptDiskRotationEnabled) - d.Set("managed_disk_cmk_key_vault_key_id", managedDiskKeyId) + var encryptDiskEncryptionSetId string if model.Properties.DiskEncryptionSetId != nil { encryptDiskEncryptionSetId = *model.Properties.DiskEncryptionSetId } d.Set("disk_encryption_set_id", encryptDiskEncryptionSetId) - var managedServicesKeyVaultId string - if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { - managedServicesKeyVaultId = v.(string) - } - d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) + return tags.FlattenAndSet(d, model.Tags) + } - var managedDiskKeyVaultId string - if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { - managedDiskKeyVaultId = v.(string) + return nil +} + +func parseWorkspaceManagedCmkKeyWithOptionalVersion(key string) (*commonids.KeyVaultKeyVersionId, error) { + var result commonids.KeyVaultKeyVersionId + if key == "" { + return nil, fmt.Errorf("parsing %q as a Key Vault Key Resource ID: string empty", key) + } + + keyId, err := commonids.ParseKeyVaultKeyID(key) + if err == nil { + result.SubscriptionId = keyId.SubscriptionId + result.ResourceGroupName = keyId.ResourceGroupName + result.VaultName = keyId.VaultName + result.KeyName = keyId.KeyName + } else { + // Try parsing resource ID as a versioned key vault key... + keyVersionId, err := commonids.ParseKeyVaultKeyVersionID(key) + if err != nil { + return nil, fmt.Errorf("parsing %q as a Key Vault Key Resource ID: %+v", key, err) } - d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) - return tags.FlattenAndSet(d, model.Tags) + result.SubscriptionId = keyVersionId.SubscriptionId + result.ResourceGroupName = keyVersionId.ResourceGroupName + result.VaultName = keyVersionId.VaultName + result.KeyName = keyVersionId.KeyName + result.VersionName = keyVersionId.VersionName } - return nil + return &result, nil } func resourceDatabricksWorkspaceDelete(d *pluginsdk.ResourceData, meta interface{}) error { diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index 595383a4ccd8..333e2856f049 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -297,7 +298,11 @@ func TestAccDatabricksWorkspace_managedServicesRootDbfsCMKAndPrivateLink(t *test }) } -func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { +func TestAccDatabricksWorkspace_altSubscriptionCmkComplete_ThreePointOh(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("Skipping: Test is only valid for v3.x providers") + } + altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -319,7 +324,37 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { }) } -func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { +func TestAccDatabricksWorkspace_altSubscriptionCmkKeysInDifferentKeyVaultsAcrossDifferentSubscriptions_ThreePointOh(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("Skipping: Test is only valid for v3.x providers") + } + + altSubscription := altSubscriptionCheck() + + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` and `ARM_TENANT_ID` environment variables to be specified") + } + + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + databricksPrincipalID := getDatabricksPrincipalId(data.Client().SubscriptionID) + r := DatabricksWorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.altSubscriptionCmkKeysInDifferentKeyVaultsAcrossDifferentSubscriptions(data, databricksPrincipalID, altSubscription), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("managed_services_cmk_key_vault_id", "managed_disk_cmk_key_vault_id"), + }) +} + +func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly_ThreePointOh(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("Skipping: Test is only valid for v3.x providers") + } + altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -341,7 +376,11 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { }) } -func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly(t *testing.T) { +func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly_ThreePointOh(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("Skipping: Test is only valid for v3.x providers") + } + altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -2072,6 +2111,208 @@ resource "azurerm_key_vault_access_policy" "managed" { `, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, alt.tenant_id, alt.subscription_id) } +// TODO: Finish this test... +func (DatabricksWorkspaceResource) altSubscriptionCmkKeysInDifferentKeyVaultsAcrossDifferentSubscriptions(data acceptance.TestData, databricksPrincipalID string, alt *DatabricksWorkspaceAlternateSubscription) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + +provider "azurerm-alt" { + features {} + + tenant_id = "%[5]s" + subscription_id = "%[6]s" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-pri-sub-diff-%[1]d" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVault" { + name = "acctestRG-databricks-kv-pri-sub-diff-%[1]d" + location = "West Europe" +} + +resource "azurerm_resource_group" "keyVaultAlt" { + provider = azurerm-alt + + name = "acctestRG-databricks-kv-alt-sub-diff-%[1]d" + location = "West Europe" +} + +resource "azurerm_databricks_workspace" "test" { + depends_on = [azurerm_key_vault_access_policy.managed] + + name = "acctest-databricks-pri-sub-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "premium" + managed_resource_group_name = "databricks-pri-sub-managed-rg-%[1]d" + + managed_services_cmk_key_vault_id = azurerm_key_vault.keyVaultAlt.id + managed_services_cmk_key_vault_key_id = azurerm_key_vault_key.servicesAlt.id + + managed_disk_cmk_key_vault_id = azurerm_key_vault.keyVault.id + managed_disk_cmk_key_vault_key_id = azurerm_key_vault_key.disk.id + + tags = { + Environment = "Sandbox" + Pricing = "Premium" + } +} + +resource "azurerm_key_vault" "keyVault" { + name = "kv-prisub-%[3]s" + resource_group_name = azurerm_resource_group.keyVault.name + location = azurerm_resource_group.keyVault.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +# Create this in a different subscription... +resource "azurerm_key_vault" "keyVaultAlt" { + provider = azurerm-alt + + name = "kv-altsub-%[3]s" + resource_group_name = azurerm_resource_group.keyVaultAlt.name + location = azurerm_resource_group.keyVaultAlt.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_key" "servicesAlt" { + provider = azurerm-alt + depends_on = [azurerm_key_vault_access_policy.terraformAlt] + + name = "acctest-services-certificate" + key_vault_id = azurerm_key_vault.keyVaultAlt.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_key_vault_key" "disk" { + depends_on = [azurerm_key_vault_access_policy.terraform] + + name = "acctest-disk-certificate" + key_vault_id = azurerm_key_vault.keyVault.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.keyVault.id + tenant_id = azurerm_key_vault.keyVault.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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "terraformAlt" { + provider = azurerm-alt + + key_vault_id = azurerm_key_vault.keyVaultAlt.id + tenant_id = azurerm_key_vault.keyVaultAlt.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", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "managed" { + key_vault_id = azurerm_key_vault.keyVault.id + tenant_id = azurerm_key_vault.keyVault.tenant_id + object_id = "%[4]s" + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} + +resource "azurerm_key_vault_access_policy" "managedAlt" { + provider = azurerm-alt + + key_vault_id = azurerm_key_vault.keyVaultAlt.id + tenant_id = azurerm_key_vault.keyVaultAlt.tenant_id + object_id = "%[4]s" + + key_permissions = [ + "Get", + "UnwrapKey", + "WrapKey", + "GetRotationPolicy", + "SetRotationPolicy", + ] +} +`, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, alt.tenant_id, alt.subscription_id) +} + func (DatabricksWorkspaceResource) altSubscriptionCmkServicesOnly(data acceptance.TestData, databricksPrincipalID string, alt *DatabricksWorkspaceAlternateSubscription) string { return fmt.Sprintf(` provider "azurerm" { From f2366b975baee3d9140bc1955227d445c3efc25d Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 21 Mar 2024 01:48:29 -0600 Subject: [PATCH 25/28] Update v4.0 RequiredWith schema attribute for managed_disk_cmk_rotation_to_latest_version_enabled field... --- .../databricks_workspace_resource.go | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 9e4da222ef88..6f8009827b88 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -114,12 +114,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { }, }, - "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, @@ -374,6 +368,13 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { ValidateFunc: keyVaultValidate.KeyVaultChildID, Deprecated: "`managed_disk_cmk_key_vault_key_id` will be removed in favour of the property `managed_disk_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", } + + // Old Reference... + resource.Schema["managed_disk_cmk_rotation_to_latest_version_enabled"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeBool, + Optional: true, + RequiredWith: []string{"managed_disk_cmk_key_vault_key_id"}, + } } else { // NOTE: These fields maybe versioned or versionless... resource.Schema["managed_services_cmk_key_vault_key_resource_id"] = &pluginsdk.Schema{ @@ -387,6 +388,13 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { Optional: true, ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), } + + // TODO: Make sure I updated this reference in the code below, see // Old Reference above... + resource.Schema["managed_disk_cmk_rotation_to_latest_version_enabled"] = &pluginsdk.Schema{ + Type: pluginsdk.TypeBool, + Optional: true, + RequiredWith: []string{"managed_disk_cmk_key_vault_key_resource_id"}, + } } return resource From 6e4dd3db4160e8f6b39ad0d08adc8bdad64723e1 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:53:21 -0600 Subject: [PATCH 26/28] Added DBFS test case... --- internal/features/four_point_oh.go | 6 +- ...dbfs_customer_managed_key_resource_test.go | 163 ++++++++++++++++++ .../databricks_workspace_resource.go | 2 - .../databricks_workspace_resource_test.go | 1 + 4 files changed, 167 insertions(+), 5 deletions(-) diff --git a/internal/features/four_point_oh.go b/internal/features/four_point_oh.go index f6e7927451ad..4387a9c18f48 100644 --- a/internal/features/four_point_oh.go +++ b/internal/features/four_point_oh.go @@ -3,7 +3,7 @@ package features -import "os" +// import "os" // nolint gocritic // DeprecatedInFourPointOh returns the deprecation message if the provider @@ -28,9 +28,9 @@ func DeprecatedInFourPointOh(deprecationMessage string) string { func FourPointOh() bool { // WodansSon: Added for testing 4.0 functionality, // will comment out in final check-in... - return !(os.Getenv("TF_FOUR_POINT_OH_BETA") == "") + // return !(os.Getenv("TF_FOUR_POINT_OH_BETA") == "") - // return false + return false } // FourPointOhBeta returns whether this provider is running in 4.0 mode diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go index cfb0f91cf64a..af6a9fde3207 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -105,6 +106,34 @@ func TestAccDatabricksWorkspaceRootDbfsCustomerManagedKey_noIp(t *testing.T) { }) } +// TODO: Add v4.0 version of the ThreePointOh test... +func TestAccDatabricksWorkspaceRootDbfsCustomerManagedKey_altRootDbfsSubscriptionComplete_ThreePointOh(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("Skipping: Test is only valid for v3.x providers") + } + + altSubscription := altSubscriptionCheck() + + if altSubscription == nil { + t.Skip("Skipping: Test requires `ARM_SUBSCRIPTION_ID_ALT` and `ARM_TENANT_ID` environment variables to be specified") + } + + data := acceptance.BuildTestData(t, "azurerm_databricks_workspace_root_dbfs_customer_managed_key", "test") + parent := acceptance.BuildTestData(t, "azurerm_databricks_workspace", "test") + r := DatabricksWorkspaceRootDbfsCustomerManagedKeyResource{} + cmkAltTemplate := r.cmkAltTemplate() + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.altRootDbfsSubscriptionComplete(data, cmkAltTemplate, altSubscription), + Check: acceptance.ComposeTestCheckFunc( + check.That(parent.ResourceName).ExistsInAzure(r), + ), + }, + parent.ImportStep(), + }) +} + func (DatabricksWorkspaceRootDbfsCustomerManagedKeyResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := workspaces.ParseWorkspaceID(state.ID) if err != nil { @@ -212,6 +241,18 @@ resource "azurerm_databricks_workspace_root_dbfs_customer_managed_key" "test" { ` } +func (DatabricksWorkspaceRootDbfsCustomerManagedKeyResource) cmkAltTemplate() string { + return ` +resource "azurerm_databricks_workspace_root_dbfs_customer_managed_key" "test" { + depends_on = [azurerm_key_vault_access_policy.databricks] + + workspace_id = azurerm_databricks_workspace.test.id + key_vault_key_id = azurerm_key_vault_key.test.id + key_vault_id = azurerm_key_vault.test.id +} +` +} + func (DatabricksWorkspaceRootDbfsCustomerManagedKeyResource) keyVaultTemplate(data acceptance.TestData) string { return fmt.Sprintf(` resource "azurerm_key_vault" "test" { @@ -283,3 +324,125 @@ resource "azurerm_key_vault_access_policy" "databricks" { } `, data.RandomInteger, data.Locations.Primary, data.RandomString) } + +func (DatabricksWorkspaceRootDbfsCustomerManagedKeyResource) keyVaultAltSubscriptionTemplate(data acceptance.TestData) string { + return fmt.Sprintf(` +resource "azurerm_key_vault" "test" { + provider = azurerm-alt + + name = "kv-altsub-%[3]s" + location = azurerm_resource_group.keyVault.location + resource_group_name = azurerm_resource_group.keyVault.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] + provider = azurerm-alt + + name = "acctest-key-%[1]d" + 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" { + provider = azurerm-alt + + 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", + "GetRotationPolicy", + "Sign", + "UnwrapKey", + "Verify", + "WrapKey", + "Delete", + "Restore", + "Recover", + "Update", + "Purge", + ] +} + +resource "azurerm_key_vault_access_policy" "databricks" { + depends_on = [azurerm_databricks_workspace.test] + provider = azurerm-alt + + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_databricks_workspace.test.storage_account_identity.0.tenant_id + object_id = azurerm_databricks_workspace.test.storage_account_identity.0.principal_id + + key_permissions = [ + "Get", + "GetRotationPolicy", + "UnwrapKey", + "WrapKey", + "Delete", + ] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r DatabricksWorkspaceRootDbfsCustomerManagedKeyResource) altRootDbfsSubscriptionComplete(data acceptance.TestData, cmkAlt string, alt *DatabricksWorkspaceAlternateSubscription) string { + keyVault := r.keyVaultAltSubscriptionTemplate(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +provider "azurerm-alt" { + features {} + + tenant_id = "%[5]s" + subscription_id = "%[6]s" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-databricks-dbfs-%[1]d" + location = "%[2]s" +} + +resource "azurerm_resource_group" "keyVault" { + provider = azurerm-alt + + name = "acctestRG-databricks-dbfs-alt-sub-%[1]d" + location = "%[2]s" +} + +%[3]s + +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" + + customer_managed_key_enabled = true + infrastructure_encryption_enabled = true +} + +%[4]s +`, data.RandomInteger, "eastus2", keyVault, cmkAlt, alt.tenant_id, alt.subscription_id) +} diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 6f8009827b88..675ce86d3160 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -369,7 +369,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { Deprecated: "`managed_disk_cmk_key_vault_key_id` will be removed in favour of the property `managed_disk_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", } - // Old Reference... resource.Schema["managed_disk_cmk_rotation_to_latest_version_enabled"] = &pluginsdk.Schema{ Type: pluginsdk.TypeBool, Optional: true, @@ -389,7 +388,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), } - // TODO: Make sure I updated this reference in the code below, see // Old Reference above... resource.Schema["managed_disk_cmk_rotation_to_latest_version_enabled"] = &pluginsdk.Schema{ Type: pluginsdk.TypeBool, Optional: true, diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index 333e2856f049..4caafb51c4db 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -298,6 +298,7 @@ func TestAccDatabricksWorkspace_managedServicesRootDbfsCMKAndPrivateLink(t *test }) } +// TODO: Add FourPointOh versions of the below ThreePointOh tests... func TestAccDatabricksWorkspace_altSubscriptionCmkComplete_ThreePointOh(t *testing.T) { if features.FourPointOhBeta() { t.Skip("Skipping: Test is only valid for v3.x providers") From 2000010558f524cd8e0e0e640671686933fbc77f Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:49:23 -0600 Subject: [PATCH 27/28] Revert 4.0 resource id changes... --- ...root_dbfs_customer_managed_key_resource.go | 405 ++++---------- ...dbfs_customer_managed_key_resource_test.go | 8 +- .../databricks_workspace_resource.go | 493 +++++------------- .../databricks_workspace_resource_test.go | 27 +- 4 files changed, 256 insertions(+), 677 deletions(-) diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index 0aa14bbe1668..81879ae6fa46 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -16,17 +16,15 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/databricks/2023-02-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/locks" keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" - "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" ) func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource { - resource := &pluginsdk.Resource{ + return &pluginsdk.Resource{ Create: databricksWorkspaceRootDbfsCustomerManagedKeyCreate, Read: databricksWorkspaceRootDbfsCustomerManagedKeyRead, Update: databricksWorkspaceRootDbfsCustomerManagedKeyUpdate, @@ -62,36 +60,20 @@ func resourceDatabricksWorkspaceRootDbfsCustomerManagedKey() *pluginsdk.Resource Required: true, ValidateFunc: workspaces.ValidateWorkspaceID, }, - }, - } - if !features.FourPointOhBeta() { - // NOTE: Added to support cross subscription cmk's in 3.x and to migrate the resource from - // using the keys data plane URL as the fields value to using the keys resource id - // instead in 4.0... - resource.Schema["key_vault_key_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: keyVaultValidate.KeyVaultChildID, - Deprecated: "`key_vault_key_id` will be removed in favour of the property `key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", - } + "key_vault_key_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + }, - resource.Schema["key_vault_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: commonids.ValidateKeyVaultID, - Deprecated: "`key_vault_id` will be removed in favour of the property `key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", - } - } else { - // NOTE: This field maybe versioned or versionless... - resource.Schema["key_vault_key_resource_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), - } + "key_vault_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + }, + }, } - - return resource } func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceData, meta interface{}) error { @@ -105,35 +87,21 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return err } - var keyIdRaw string // TODO: Remove in 4.0 - var dbfsKeyVaultId string // TODO: Remove in 4.0 - var key *keyVaultParse.NestedItemId // TODO: Remove in 4.0 - var keyVaultKeyId string - var dbfsKey *commonids.KeyVaultKeyVersionId - var dbfsKeyId string - - if !features.FourPointOhBeta() { - if v, ok := d.GetOk("key_vault_key_id"); ok { - keyIdRaw = v.(string) - } + var keyIdRaw string + var keyVaultId string + var key *keyVaultParse.NestedItemId - if v, ok := d.GetOk("key_vault_id"); ok { - dbfsKeyVaultId = v.(string) - } + if v, ok := d.GetOk("key_vault_key_id"); ok { + keyIdRaw = v.(string) + } - key, err = keyVaultParse.ParseNestedItemID(keyIdRaw) - if err != nil { - return err - } - } else { - if v, ok := d.GetOk("key_vault_key_resource_id"); ok { - keyVaultKeyId = v.(string) - } + if v, ok := d.GetOk("key_vault_id"); ok { + keyVaultId = v.(string) + } - dbfsKey, err = parseWorkspaceManagedCmkKeyWithOptionalVersion(keyVaultKeyId) - if err != nil { - return err - } + key, err = keyVaultParse.ParseNestedItemID(keyIdRaw) + if err != nil { + return err } // Not sure if I should also lock the key vault here too @@ -170,87 +138,45 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - if !features.FourPointOhBeta() { - // If the 'root_dbfs_cmk_key_vault_id' was not defined assume the - // key vault exists in the same subscription as the workspace... - rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - - // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - if dbfsKeyVaultId != "" { - keyVaultId, err := commonids.ParseKeyVaultID(dbfsKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", dbfsKeyVaultId, err) - } - - rootDbfsSubscriptionId = commonids.NewSubscriptionID(keyVaultId.SubscriptionId) - } + // If the 'root_dbfs_cmk_key_vault_id' was not defined assume the + // key vault exists in the same subscription as the workspace... + dbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) - if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) - } - - // Only throw the import error if the keysource value has been set to something other than default... - if params.Encryption != nil && params.Encryption.Value != nil && keySource != workspaces.KeySourceDefault { - return tf.ImportAsExistsError("azurerm_databricks_workspace_root_dbfs_customer_managed_key", id.ID()) + // If they passed the 'root_dbfs_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + if keyVaultId != "" { + keyVaultId, err := commonids.ParseKeyVaultID(keyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", keyVaultId, err) } - // We need to pull all of the custom params from the parent - // workspace resource and then add our new encryption values into the - // structure, else the other values set in the parent workspace - // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ - // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' - // fields have a default value in the parent workspace resource. - params.Encryption = &workspaces.WorkspaceEncryptionParameter{ - Value: &workspaces.Encryption{ - KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), - KeyName: pointer.To(key.Name), - Keyversion: pointer.To(key.Version), - Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), - }, - } - } else { - if dbfsKey != nil { - // Make sure the key vault exists - keyVaultId := commonids.NewKeyVaultID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName) - resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) - if err != nil { - return fmt.Errorf("retrieving the Resource ID for the Root DBFS Customer Managed %s: %+v", keyVaultId, err) - } - - keyVaultModel := resp.Model - if keyVaultModel == nil { - return fmt.Errorf("retrieving %s: model was nil", keyVaultId) - } + dbfsSubscriptionId = commonids.NewSubscriptionID(keyVaultId.SubscriptionId) + } - // Only throw the import error if the keysource value has been set to something other than default... - if params.Encryption != nil && params.Encryption.Value != nil && keySource != workspaces.KeySourceDefault { - return tf.ImportAsExistsError("azurerm_databricks_workspace_root_dbfs_customer_managed_key", id.ID()) - } + // make sure the key vault exists + keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, dbfsSubscriptionId, key.KeyVaultBaseUrl) + if err != nil || keyVaultIdRaw == nil { + return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err) + } - // We need to pull all of the custom params from the parent - // workspace resource and then add our new encryption values into the - // structure, else the other values set in the parent workspace - // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ - // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' - // fields have a default value in the parent workspace resource. - params.Encryption = &workspaces.WorkspaceEncryptionParameter{ - Value: &workspaces.Encryption{ - KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), - KeyName: pointer.To(dbfsKey.KeyName), - Keyvaulturi: keyVaultModel.Properties.VaultUri, - }, - } + // Only throw the import error if the keysource value has been set to something other than default... + if params.Encryption != nil && params.Encryption.Value != nil && keySource != workspaces.KeySourceDefault { + return tf.ImportAsExistsError("azurerm_databricks_workspace_root_dbfs_customer_managed_key", id.ID()) + } - if dbfsKey.VersionName == "" { - dbfsKeyId = commonids.NewKeyVaultKeyID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName, dbfsKey.KeyName).ID() - } else { - params.Encryption.Value.Keyversion = pointer.To(dbfsKey.VersionName) - dbfsKeyId = dbfsKey.ID() - } - } + // We need to pull all of the custom params from the parent + // workspace resource and then add our new encryption values into the + // structure, else the other values set in the parent workspace + // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ + // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' + // fields have a default value in the parent workspace resource. + params.Encryption = &workspaces.WorkspaceEncryptionParameter{ + Value: &workspaces.Encryption{ + KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), + KeyName: pointer.To(key.Name), + Keyversion: pointer.To(key.Version), + Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), + }, } props := pointer.From(workspace.Model) @@ -264,11 +190,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyCreate(d *pluginsdk.ResourceDa // Always set this even if it's empty to keep the state file // consistent with the configuration file... - if !features.FourPointOhBeta() { - d.Set("key_vault_id", dbfsKeyVaultId) - } else { - d.Set("key_vault_key_resource_id", dbfsKeyId) - } + d.Set("key_vault_id", keyVaultId) return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } @@ -283,6 +205,11 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData return err } + var keyVaultId string + if v, ok := d.GetOk("key_vault_id"); ok { + keyVaultId = v.(string) + } + resp, err := client.Get(ctx, *id) if err != nil { if response.WasNotFound(resp.HttpResponse) { @@ -294,75 +221,28 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData return fmt.Errorf("retrieving %s: %+v", *id, err) } - var keySource string - var keyName string - var keyVersion string - var keyVaultURI string - var dbfsKeyVaultId string // TODO: Remove in 4.0 - var dbfsKeyVaultKeyId string - var dbfsKey string - if model := resp.Model; model != nil { if model.Properties.Parameters != nil { if props := model.Properties.Parameters.Encryption; props != nil { - - if props.Value.KeySource != nil { - keySource = string(*props.Value.KeySource) - } - if props.Value.KeyName != nil { - keyName = *props.Value.KeyName + if strings.EqualFold(string(*props.Value.KeySource), string(workspaces.KeySourceMicrosoftPointKeyvault)) && (props.Value.KeyName == nil || props.Value.Keyversion == nil || props.Value.Keyvaulturi == nil) { + d.SetId("") + return nil } - if props.Value.Keyversion != nil { - keyVersion = *props.Value.Keyversion - } - if props.Value.Keyvaulturi != nil { - keyVaultURI = *props.Value.Keyvaulturi + + key, err := keyVaultParse.NewNestedItemID(string(*props.Value.Keyvaulturi), keyVaultParse.NestedItemTypeKey, string(*props.Value.KeyName), string(*props.Value.Keyversion)) + if err == nil { + d.Set("key_vault_key_id", key.ID()) } } } } - if strings.EqualFold(keySource, string(workspaces.KeySourceMicrosoftPointKeyvault)) && (keyName == "" || keyVersion == "" || keyVaultURI == "") { - d.SetId("") - return nil - } - d.SetId(id.ID()) d.Set("workspace_id", id.ID()) - if !features.FourPointOhBeta() { - if keyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(keyVaultURI, keyVaultParse.NestedItemTypeKey, keyName, keyVersion) - if err == nil { - d.Set("key_vault_key_id", key.ID()) - } - } - - // Always set this even if it's empty to keep the state file - // consistent with the configuration file... - if v, ok := d.GetOk("key_vault_id"); ok { - dbfsKeyVaultId = v.(string) - } - d.Set("key_vault_id", dbfsKeyVaultId) - } else { - // I have to pull this from state because there is no where - // to grab the subscription value from... - if v, ok := d.GetOk("key_vault_key_resource_id"); ok { - dbfsKey = v.(string) - } - - if dbfsKey != "" { - key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(dbfsKey) - if err == nil { - if key.VersionName == "" { - dbfsKeyVaultKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() - } else { - dbfsKeyVaultKeyId = key.ID() - } - } - } - d.Set("key_vault_key_resource_id", dbfsKeyVaultKeyId) - } + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + d.Set("key_vault_id", keyVaultId) return nil } @@ -378,31 +258,22 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return err } - var key *keyVaultParse.NestedItemId // TODO: Remove in 4.0 - var dbfsKeyVaultId string // TODO: Remove in 4.0 - + var key *keyVaultParse.NestedItemId var params *workspaces.WorkspaceCustomParameters - var dbfsKey *commonids.KeyVaultKeyVersionId - var dbfsKeyId string - - keyVaultKeyId := d.Get("key_vault_key_resource_id").(string) + var keyVaultId string + var keyVaultKeyId string - if !features.FourPointOhBeta() { - var keyVaultKeyIdRaw string + if v, ok := d.GetOk("key_vault_key_id"); ok { + keyVaultKeyId = v.(string) + } - if v, ok := d.GetOk("key_vault_key_id"); ok { - keyVaultKeyIdRaw = v.(string) - } + if v, ok := d.GetOk("key_vault_id"); ok { + keyVaultId = v.(string) + } - key, err = keyVaultParse.ParseNestedItemID(keyVaultKeyIdRaw) - if err != nil { - return err - } - } else { - dbfsKey, err = parseWorkspaceManagedCmkKeyWithOptionalVersion(keyVaultKeyId) - if err != nil { - return err - } + key, err = keyVaultParse.ParseNestedItemID(keyVaultKeyId) + if err != nil { + return err } // Not sure if I should also lock the key vault here too @@ -432,80 +303,38 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("%s: `customer_managed_key_enabled` must be set to `true`", *id) } - // TODO: Remove in 4.0 - if v, ok := d.GetOk("key_vault_id"); ok { - dbfsKeyVaultId = v.(string) - } - - if !features.FourPointOhBeta() { - // If the 'root_dbfs_cmk_key_vault_id' was not defined assume - // the key vault exists in the same subscription as the workspace... - rootDbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - - if dbfsKeyVaultId != "" { - v, err := commonids.ParseKeyVaultID(dbfsKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", dbfsKeyVaultId, err) - } - - rootDbfsSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) - } + // If the 'root_dbfs_cmk_key_vault_id' was not defined assume + // the key vault exists in the same subscription as the workspace... + dbfsSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - // make sure the key vault exists - keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, rootDbfsSubscriptionId, key.KeyVaultBaseUrl) - if err != nil || keyVaultIdRaw == nil { - return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", rootDbfsSubscriptionId, key.KeyVaultBaseUrl, err) - } - - // We need to pull all of the custom params from the parent - // workspace resource and then add our new encryption values into the - // structure, else the other values set in the parent workspace - // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ - // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' - // fields have a default value in the parent workspace resource. - params.Encryption = &workspaces.WorkspaceEncryptionParameter{ - Value: &workspaces.Encryption{ - KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), - KeyName: pointer.To(key.Name), - Keyversion: pointer.To(key.Version), - Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), - }, + if keyVaultId != "" { + v, err := commonids.ParseKeyVaultID(keyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", keyVaultId, err) } - } else { - if dbfsKey != nil { - // Make sure the key vault exists - keyVaultId := commonids.NewKeyVaultID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName) - resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) - if err != nil { - return fmt.Errorf("retrieving the Resource ID for the Root DBFS Customer Managed %s: %+v", keyVaultId, err) - } - keyVaultModel := resp.Model - if keyVaultModel == nil { - return fmt.Errorf("retrieving %s: model was nil", keyVaultId) - } + dbfsSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + } - // We need to pull all of the custom params from the parent - // workspace resource and then add our new encryption values into the - // structure, else the other values set in the parent workspace - // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ - // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' - // fields have a default value in the parent workspace resource. - params.Encryption = &workspaces.WorkspaceEncryptionParameter{ - Value: &workspaces.Encryption{ - KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), - KeyName: pointer.To(dbfsKey.KeyName), - Keyvaulturi: keyVaultModel.Properties.VaultUri, - }, - } + // make sure the key vault exists + _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, dbfsSubscriptionId, key.KeyVaultBaseUrl) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the Key Vault in subscription %q at URL %q: %+v", dbfsSubscriptionId, key.KeyVaultBaseUrl, err) + } - if dbfsKey.VersionName != "" { - params.Encryption.Value.Keyversion = pointer.To(dbfsKey.VersionName) - dbfsKeyId = dbfsKey.ID() - } else { - dbfsKeyId = commonids.NewKeyVaultKeyID(dbfsKey.SubscriptionId, dbfsKey.ResourceGroupName, dbfsKey.VaultName, dbfsKey.KeyName).ID() - } - } + // We need to pull all of the custom params from the parent + // workspace resource and then add our new encryption values into the + // structure, else the other values set in the parent workspace + // resource will be lost and overwritten as nil. ¯\_(ツ)_/¯ + // NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled' + // fields have a default value in the parent workspace resource. + params.Encryption = &workspaces.WorkspaceEncryptionParameter{ + Value: &workspaces.Encryption{ + KeySource: pointer.To(workspaces.KeySourceMicrosoftPointKeyvault), + KeyName: pointer.To(key.Name), + Keyversion: pointer.To(key.Version), + Keyvaulturi: pointer.To(key.KeyVaultBaseUrl), + }, } props := pointer.From(workspace.Model) @@ -515,13 +344,9 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyUpdate(d *pluginsdk.ResourceDa return fmt.Errorf("updating Root DBFS Customer Managed Key for %s: %+v", *id, err) } - if !features.FourPointOhBeta() { - // Always set this even if it's empty to keep the state file - // consistent with the configuration file... - d.Set("key_vault_id", dbfsKeyVaultId) - } else { - d.Set("key_vault_key_resource_id", dbfsKeyId) - } + // Always set this even if it's empty to keep the state file + // consistent with the configuration file... + d.Set("key_vault_id", keyVaultId) return databricksWorkspaceRootDbfsCustomerManagedKeyRead(d, meta) } diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go index af6a9fde3207..5b1decc65765 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource_test.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -106,12 +105,7 @@ func TestAccDatabricksWorkspaceRootDbfsCustomerManagedKey_noIp(t *testing.T) { }) } -// TODO: Add v4.0 version of the ThreePointOh test... -func TestAccDatabricksWorkspaceRootDbfsCustomerManagedKey_altRootDbfsSubscriptionComplete_ThreePointOh(t *testing.T) { - if features.FourPointOhBeta() { - t.Skip("Skipping: Test is only valid for v3.x providers") - } - +func TestAccDatabricksWorkspaceRootDbfsCustomerManagedKey_altRootDbfsSubscriptionComplete(t *testing.T) { altSubscription := altSubscriptionCheck() if altSubscription == nil { diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 675ce86d3160..84e0324d2804 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -21,7 +21,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/locks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/databricks/validate" keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" @@ -35,7 +34,7 @@ import ( ) func resourceDatabricksWorkspace() *pluginsdk.Resource { - resource := &pluginsdk.Resource{ + return &pluginsdk.Resource{ Create: resourceDatabricksWorkspaceCreateUpdate, Read: resourceDatabricksWorkspaceRead, Update: resourceDatabricksWorkspaceCreateUpdate, @@ -263,6 +262,35 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { Computed: true, }, + "managed_services_cmk_key_vault_key_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + }, + + "managed_services_cmk_key_vault_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + }, + + "managed_disk_cmk_key_vault_key_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.KeyVaultChildID, + }, + "managed_disk_cmk_key_vault_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateKeyVaultID, + }, + + "managed_disk_cmk_rotation_to_latest_version_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + RequiredWith: []string{"managed_disk_cmk_key_vault_key_id"}, + }, + "disk_encryption_set_id": { Type: pluginsdk.TypeString, Computed: true, @@ -336,66 +364,6 @@ func resourceDatabricksWorkspace() *pluginsdk.Resource { return nil }), } - - if !features.FourPointOhBeta() { - // NOTE: Added to support cross subscription cmk's in 3.x and to migrate the resource from - // using the keys data plane URL as the fields value to using the keys resource id - // instead in 4.0... - resource.Schema["managed_services_cmk_key_vault_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: commonids.ValidateKeyVaultID, - Deprecated: "`managed_services_cmk_key_vault_id` will be removed in favour of the property `managed_services_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", - } - - resource.Schema["managed_services_cmk_key_vault_key_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: keyVaultValidate.KeyVaultChildID, - Deprecated: "`managed_services_cmk_key_vault_key_id` will be removed in favour of the property `managed_services_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", - } - - resource.Schema["managed_disk_cmk_key_vault_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: commonids.ValidateKeyVaultID, - Deprecated: "`managed_disk_cmk_key_vault_id` will be removed in favour of the property `managed_disk_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", - } - - resource.Schema["managed_disk_cmk_key_vault_key_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: keyVaultValidate.KeyVaultChildID, - Deprecated: "`managed_disk_cmk_key_vault_key_id` will be removed in favour of the property `managed_disk_cmk_key_vault_key_resource_id` in version 4.0 of the AzureRM Provider.", - } - - resource.Schema["managed_disk_cmk_rotation_to_latest_version_enabled"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeBool, - Optional: true, - RequiredWith: []string{"managed_disk_cmk_key_vault_key_id"}, - } - } else { - // NOTE: These fields maybe versioned or versionless... - resource.Schema["managed_services_cmk_key_vault_key_resource_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), - } - - resource.Schema["managed_disk_cmk_key_vault_key_resource_id"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.Any(commonids.ValidateKeyVaultKeyID, commonids.ValidateKeyVaultKeyVersionID), - } - - resource.Schema["managed_disk_cmk_rotation_to_latest_version_enabled"] = &pluginsdk.Schema{ - Type: pluginsdk.TypeBool, - Optional: true, - RequiredWith: []string{"managed_disk_cmk_key_vault_key_resource_id"}, - } - } - - return resource } func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { @@ -498,188 +466,96 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int encrypt := &workspaces.WorkspacePropertiesEncryption{} encrypt.Entities = workspaces.EncryptionEntitiesDefinition{} - // TODO: Remove in 4.0 - var managedServicesKeyIdRaw string - var managedServicesKeyVaultId string - var managedDiskKeyIdRaw string - var managedDiskKeyVaultId string - - // NOTE: Keep in 4.0 - var serviceKeyIdRaw string - var diskKeyIdRaw string var servicesKeyId string + var servicesKeyVaultId string var diskKeyId string + var diskKeyVaultId string - if !features.FourPointOhBeta() { - // set default subscription as current subscription for key vault look-up... - managedServicesResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - managedDiskResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - - if v, ok := d.GetOk("managed_services_cmk_key_vault_key_id"); ok { - managedServicesKeyIdRaw = v.(string) - } + if v, ok := d.GetOk("managed_services_cmk_key_vault_key_id"); ok { + servicesKeyId = v.(string) + } - if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { - managedServicesKeyVaultId = v.(string) - } + if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { + servicesKeyVaultId = v.(string) + } - if v, ok := d.GetOk("managed_disk_cmk_key_vault_key_id"); ok { - managedDiskKeyIdRaw = v.(string) - } + if v, ok := d.GetOk("managed_disk_cmk_key_vault_key_id"); ok { + diskKeyId = v.(string) + } - if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { - managedDiskKeyVaultId = v.(string) - } + if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { + diskKeyVaultId = v.(string) + } - if managedServicesKeyVaultId != "" { - // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedServicesKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedServicesKeyVaultId, err) - } + // set default subscription as current subscription for key vault look-up... + servicesResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) + diskResourceSubscriptionId := commonids.NewSubscriptionID(id.SubscriptionId) - managedServicesResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + if servicesKeyVaultId != "" { + // If they passed the 'managed_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(servicesKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", servicesKeyVaultId, err) } - if managedServicesKeyIdRaw != "" { - setEncrypt = true - key, err := keyVaultParse.ParseNestedItemID(managedServicesKeyIdRaw) - if err != nil { - return err - } - - // make sure the key vault exists - _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl) - if err != nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", managedServicesResourceSubscriptionId, key.KeyVaultBaseUrl, err) - } + servicesResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + } - encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ - KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, - KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ - KeyName: key.Name, - KeyVersion: key.Version, - KeyVaultUri: key.KeyVaultBaseUrl, - }, - } + if servicesKeyId != "" { + setEncrypt = true + key, err := keyVaultParse.ParseNestedItemID(servicesKeyId) + if err != nil { + return err } - if managedDiskKeyVaultId != "" { - // If they passed the 'managed_disk_cmk_key_vault_id' parse the Key Vault ID - // to extract the correct key vault subscription for the exists call... - v, err := commonids.ParseKeyVaultID(managedDiskKeyVaultId) - if err != nil { - return fmt.Errorf("parsing %q as a Key Vault ID: %+v", managedDiskKeyVaultId, err) - } - - managedDiskResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + // make sure the key vault exists + _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, servicesResourceSubscriptionId, key.KeyVaultBaseUrl) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault in subscription %q at URL %q: %+v", servicesResourceSubscriptionId, key.KeyVaultBaseUrl, err) } - if managedDiskKeyIdRaw != "" { - setEncrypt = true - key, err := keyVaultParse.ParseNestedItemID(managedDiskKeyIdRaw) - if err != nil { - return err - } - - // make sure the key vault exists - _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl) - if err != nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", managedDiskResourceSubscriptionId, key.KeyVaultBaseUrl, err) - } - - encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ - KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, - KeyVaultProperties: workspaces.ManagedDiskEncryptionKeyVaultProperties{ - KeyName: key.Name, - KeyVersion: key.Version, - KeyVaultUri: key.KeyVaultBaseUrl, - }, - } - } - } else { - // Migrate to new 4.0 resource ID fields... - if v, ok := d.GetOk("managed_services_cmk_key_vault_key_resource_id"); ok { - serviceKeyIdRaw = v.(string) + encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ + KeyName: key.Name, + KeyVersion: key.Version, + KeyVaultUri: key.KeyVaultBaseUrl, + }, } + } - if v, ok := d.GetOk("managed_disk_cmk_key_vault_key_resource_id"); ok { - diskKeyIdRaw = v.(string) + if diskKeyVaultId != "" { + // If they passed the 'managed_disk_cmk_key_vault_id' parse the Key Vault ID + // to extract the correct key vault subscription for the exists call... + v, err := commonids.ParseKeyVaultID(diskKeyVaultId) + if err != nil { + return fmt.Errorf("parsing %q as a Key Vault ID: %+v", diskKeyVaultId, err) } - if serviceKeyIdRaw != "" { - setEncrypt = true - - // NOTE: The key ID may or may not be versionless... - key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(serviceKeyIdRaw) - if err != nil { - return err - } - - // Make sure the key vault exists - keyVaultId := commonids.NewKeyVaultID(key.SubscriptionId, key.ResourceGroupName, key.VaultName) - resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) - if err != nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed services Key Vault %q in subscription %q: %+v", keyVaultId, key.SubscriptionId, err) - } - - model := resp.Model - if model == nil { - return fmt.Errorf("retrieving %s: model was nil", keyVaultId) - } - - encrypt.Entities.ManagedServices = &workspaces.EncryptionV2{ - KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, - KeyVaultProperties: &workspaces.EncryptionV2KeyVaultProperties{ - KeyName: key.KeyName, - KeyVaultUri: *model.Properties.VaultUri, - }, - } + diskResourceSubscriptionId = commonids.NewSubscriptionID(v.SubscriptionId) + } - if key.VersionName == "" { - servicesKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() - } else { - encrypt.Entities.ManagedServices.KeyVaultProperties.KeyVersion = key.VersionName - servicesKeyId = key.ID() - } + if diskKeyId != "" { + setEncrypt = true + key, err := keyVaultParse.ParseNestedItemID(diskKeyId) + if err != nil { + return err } - if diskKeyIdRaw != "" { - setEncrypt = true - - // NOTE: The key ID may or may not be versionless... - key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(diskKeyIdRaw) - if err != nil { - return err - } - - // Make sure the key vault exists - keyVaultId := commonids.NewKeyVaultID(key.SubscriptionId, key.ResourceGroupName, key.VaultName) - resp, err := keyVaultsClient.VaultsClient.Get(ctx, keyVaultId) - if err != nil { - return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault %q in subscription %q: %+v", keyVaultId, key.SubscriptionId, err) - } - - model := resp.Model - if model == nil { - return fmt.Errorf("retrieving %s: model was nil", keyVaultId) - } - - encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ - KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, - KeyVaultProperties: workspaces.ManagedDiskEncryptionKeyVaultProperties{ - KeyName: key.KeyName, - KeyVaultUri: *model.Properties.VaultUri, - }, - } + // make sure the key vault exists + _, err = keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, diskResourceSubscriptionId, key.KeyVaultBaseUrl) + if err != nil { + return fmt.Errorf("retrieving the Resource ID for the customer-managed keys for managed disk Key Vault in subscription %q at URL %q: %+v", diskResourceSubscriptionId, key.KeyVaultBaseUrl, err) + } - if key.VersionName == "" { - diskKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() - } else { - encrypt.Entities.ManagedDisk.KeyVaultProperties.KeyVersion = key.VersionName - diskKeyId = key.ID() - } + encrypt.Entities.ManagedDisk = &workspaces.ManagedDiskEncryption{ + KeySource: workspaces.EncryptionKeySourceMicrosoftPointKeyvault, + KeyVaultProperties: workspaces.ManagedDiskEncryptionKeyVaultProperties{ + KeyName: key.Name, + KeyVersion: key.Version, + KeyVaultUri: key.KeyVaultBaseUrl, + }, } } @@ -744,15 +620,10 @@ func resourceDatabricksWorkspaceCreateUpdate(d *pluginsdk.ResourceData, meta int // Always set these even if they are empty to keep the state file // consistent with the configuration file... - if !features.FourPointOhBeta() { - d.Set("managed_services_cmk_key_vault_key_id", managedServicesKeyIdRaw) - d.Set("managed_disk_cmk_key_vault_key_id", managedDiskKeyIdRaw) - d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) - d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) - } else { - d.Set("managed_services_cmk_key_vault_key_resource_id", servicesKeyId) - d.Set("managed_disk_cmk_key_vault_key_resource_id", diskKeyId) - } + d.Set("managed_services_cmk_key_vault_key_id", servicesKeyId) + d.Set("managed_disk_cmk_key_vault_key_id", diskKeyId) + d.Set("managed_services_cmk_key_vault_id", servicesKeyVaultId) + d.Set("managed_disk_cmk_key_vault_id", diskKeyVaultId) return resourceDatabricksWorkspaceRead(d, meta) } @@ -767,6 +638,17 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} return err } + var encryptDiskRotationEnabled bool + var servicesKeyVaultId string + if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { + servicesKeyVaultId = v.(string) + } + + var diskKeyVaultId string + if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { + diskKeyVaultId = v.(string) + } + resp, err := client.Get(ctx, *id) if err != nil { if response.WasNotFound(resp.HttpResponse) { @@ -850,157 +732,54 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} } d.Set("workspace_id", workspaceId) - var encryptDiskRotationEnabled bool - - if !features.FourPointOhBeta() { - var managedServicesKeyName string - var managedServicesKeyVersion string - var managedServicesKeyVaultURI string - var managedServicesKeyId string - var managedDiskKeyName string - var managedDiskKeyVersion string - var managedDiskKeyVaultURI string - var managedDiskKeyId string - - // customer managed key for managed services - if encryption := model.Properties.Encryption; encryption != nil { - if encryptionProps := encryption.Entities.ManagedServices; encryptionProps != nil { - managedServicesKeyName = encryptionProps.KeyVaultProperties.KeyName - managedServicesKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion - managedServicesKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri - - if managedServicesKeyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(managedServicesKeyVaultURI, keyVaultParse.NestedItemTypeKey, managedServicesKeyName, managedServicesKeyVersion) - if err == nil { - managedServicesKeyId = key.ID() - } - } - } - } - d.Set("managed_services_cmk_key_vault_key_id", managedServicesKeyId) - - // customer managed key for managed disk - if encryption := model.Properties.Encryption; encryption != nil { - if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { - managedDiskKeyName = encryptionProps.KeyVaultProperties.KeyName - managedDiskKeyVersion = encryptionProps.KeyVaultProperties.KeyVersion - managedDiskKeyVaultURI = encryptionProps.KeyVaultProperties.KeyVaultUri - encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled - } - - if managedDiskKeyVaultURI != "" { - key, err := keyVaultParse.NewNestedItemID(managedDiskKeyVaultURI, keyVaultParse.NestedItemTypeKey, managedDiskKeyName, managedDiskKeyVersion) + // customer managed key for managed services + var servicesKeyId string + if encryption := model.Properties.Encryption; encryption != nil { + if encryptionProps := encryption.Entities.ManagedServices; encryptionProps != nil { + if encryptionProps.KeyVaultProperties.KeyVaultUri != "" { + key, err := keyVaultParse.NewNestedItemID(encryptionProps.KeyVaultProperties.KeyVaultUri, keyVaultParse.NestedItemTypeKey, encryptionProps.KeyVaultProperties.KeyName, encryptionProps.KeyVaultProperties.KeyVersion) if err == nil { - managedDiskKeyId = key.ID() - } - } - } - d.Set("managed_disk_cmk_key_vault_key_id", managedDiskKeyId) - - var managedServicesKeyVaultId string - if v, ok := d.GetOk("managed_services_cmk_key_vault_id"); ok { - managedServicesKeyVaultId = v.(string) - } - d.Set("managed_services_cmk_key_vault_id", managedServicesKeyVaultId) - - var managedDiskKeyVaultId string - if v, ok := d.GetOk("managed_disk_cmk_key_vault_id"); ok { - managedDiskKeyVaultId = v.(string) - } - d.Set("managed_disk_cmk_key_vault_id", managedDiskKeyVaultId) - } else { - // Set new 4.0 values... - var servicesKeyIdRaw string - var diskKeyIdRaw string - var servicesKeyId string - var diskKeyId string - - // NOTE: I have to pull this from state else I won't know what subscription - // to use in the call to the NewKeyVaultKeyID/NewKeyVaultKeyVersionID function... - if v := d.Get("managed_services_cmk_key_vault_key_resource_id"); v != nil { - servicesKeyIdRaw = v.(string) - } - - if v := d.Get("managed_disk_cmk_key_vault_key_resource_id"); v != nil { - diskKeyIdRaw = v.(string) - } - - // customer managed key for managed services - if servicesKeyIdRaw != "" { - key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(servicesKeyIdRaw) - if err == nil { - if key.VersionName == "" { - servicesKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() - } else { servicesKeyId = key.ID() } } } - d.Set("managed_services_cmk_key_vault_key_resource_id", servicesKeyId) - - // customer managed key for managed disk - if diskKeyIdRaw != "" { - key, err := parseWorkspaceManagedCmkKeyWithOptionalVersion(diskKeyIdRaw) - if err == nil { - if key.VersionName == "" { - diskKeyId = commonids.NewKeyVaultKeyID(key.SubscriptionId, key.ResourceGroupName, key.VaultName, key.KeyName).ID() - } else { + } + + // customer managed key for managed disk + var diskKeyId string + if encryption := model.Properties.Encryption; encryption != nil { + if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { + if encryptionProps.KeyVaultProperties.KeyVaultUri != "" { + key, err := keyVaultParse.NewNestedItemID(encryptionProps.KeyVaultProperties.KeyVaultUri, keyVaultParse.NestedItemTypeKey, encryptionProps.KeyVaultProperties.KeyName, encryptionProps.KeyVaultProperties.KeyVersion) + if err == nil { diskKeyId = key.ID() } } - if encryption := model.Properties.Encryption; encryption != nil { - if encryptionProps := encryption.Entities.ManagedDisk; encryptionProps != nil { - encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled - } - } + encryptDiskRotationEnabled = *encryptionProps.RotationToLatestKeyVersionEnabled } - d.Set("managed_disk_cmk_key_vault_key_resource_id", diskKeyId) } - d.Set("managed_disk_cmk_rotation_to_latest_version_enabled", encryptDiskRotationEnabled) - var encryptDiskEncryptionSetId string if model.Properties.DiskEncryptionSetId != nil { encryptDiskEncryptionSetId = *model.Properties.DiskEncryptionSetId } d.Set("disk_encryption_set_id", encryptDiskEncryptionSetId) + // Always set these even if they are empty to keep the state file + // consistent with the configuration file... + d.Set("managed_services_cmk_key_vault_key_id", servicesKeyId) + d.Set("managed_services_cmk_key_vault_id", servicesKeyVaultId) + d.Set("managed_disk_cmk_key_vault_key_id", diskKeyId) + d.Set("managed_disk_cmk_key_vault_id", diskKeyVaultId) + d.Set("managed_disk_cmk_rotation_to_latest_version_enabled", encryptDiskRotationEnabled) + return tags.FlattenAndSet(d, model.Tags) } return nil } -func parseWorkspaceManagedCmkKeyWithOptionalVersion(key string) (*commonids.KeyVaultKeyVersionId, error) { - var result commonids.KeyVaultKeyVersionId - if key == "" { - return nil, fmt.Errorf("parsing %q as a Key Vault Key Resource ID: string empty", key) - } - - keyId, err := commonids.ParseKeyVaultKeyID(key) - if err == nil { - result.SubscriptionId = keyId.SubscriptionId - result.ResourceGroupName = keyId.ResourceGroupName - result.VaultName = keyId.VaultName - result.KeyName = keyId.KeyName - } else { - // Try parsing resource ID as a versioned key vault key... - keyVersionId, err := commonids.ParseKeyVaultKeyVersionID(key) - if err != nil { - return nil, fmt.Errorf("parsing %q as a Key Vault Key Resource ID: %+v", key, err) - } - - result.SubscriptionId = keyVersionId.SubscriptionId - result.ResourceGroupName = keyVersionId.ResourceGroupName - result.VaultName = keyVersionId.VaultName - result.KeyName = keyVersionId.KeyName - result.VersionName = keyVersionId.VersionName - } - - return &result, nil -} - func resourceDatabricksWorkspaceDelete(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).DataBricks.WorkspacesClient ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) diff --git a/internal/services/databricks/databricks_workspace_resource_test.go b/internal/services/databricks/databricks_workspace_resource_test.go index 4caafb51c4db..1d09f5b3e4b2 100644 --- a/internal/services/databricks/databricks_workspace_resource_test.go +++ b/internal/services/databricks/databricks_workspace_resource_test.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -298,12 +297,7 @@ func TestAccDatabricksWorkspace_managedServicesRootDbfsCMKAndPrivateLink(t *test }) } -// TODO: Add FourPointOh versions of the below ThreePointOh tests... -func TestAccDatabricksWorkspace_altSubscriptionCmkComplete_ThreePointOh(t *testing.T) { - if features.FourPointOhBeta() { - t.Skip("Skipping: Test is only valid for v3.x providers") - } - +func TestAccDatabricksWorkspace_altSubscriptionCmkComplete(t *testing.T) { altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -325,11 +319,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkComplete_ThreePointOh(t *testi }) } -func TestAccDatabricksWorkspace_altSubscriptionCmkKeysInDifferentKeyVaultsAcrossDifferentSubscriptions_ThreePointOh(t *testing.T) { - if features.FourPointOhBeta() { - t.Skip("Skipping: Test is only valid for v3.x providers") - } - +func TestAccDatabricksWorkspace_altSubscriptionCmkKeysInDifferentKeyVaultsAcrossDifferentSubscriptions(t *testing.T) { altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -351,11 +341,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkKeysInDifferentKeyVaultsAcross }) } -func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly_ThreePointOh(t *testing.T) { - if features.FourPointOhBeta() { - t.Skip("Skipping: Test is only valid for v3.x providers") - } - +func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly(t *testing.T) { altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -377,11 +363,7 @@ func TestAccDatabricksWorkspace_altSubscriptionCmkServicesOnly_ThreePointOh(t *t }) } -func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly_ThreePointOh(t *testing.T) { - if features.FourPointOhBeta() { - t.Skip("Skipping: Test is only valid for v3.x providers") - } - +func TestAccDatabricksWorkspace_altSubscriptionCmkDiskOnly(t *testing.T) { altSubscription := altSubscriptionCheck() if altSubscription == nil { @@ -2112,7 +2094,6 @@ resource "azurerm_key_vault_access_policy" "managed" { `, data.RandomInteger, data.Locations.Secondary, data.RandomString, databricksPrincipalID, alt.tenant_id, alt.subscription_id) } -// TODO: Finish this test... func (DatabricksWorkspaceResource) altSubscriptionCmkKeysInDifferentKeyVaultsAcrossDifferentSubscriptions(data acceptance.TestData, databricksPrincipalID string, alt *DatabricksWorkspaceAlternateSubscription) string { return fmt.Sprintf(` provider "azurerm" { From 47fce8c7ff7e8ba76bc9fd245638bdd8b947dfd2 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:10:34 -0600 Subject: [PATCH 28/28] Fix lint error... --- .../databricks_root_dbfs_customer_managed_key_resource.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go index 81879ae6fa46..1ebe5df87905 100644 --- a/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go +++ b/internal/services/databricks/databricks_root_dbfs_customer_managed_key_resource.go @@ -229,7 +229,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData return nil } - key, err := keyVaultParse.NewNestedItemID(string(*props.Value.Keyvaulturi), keyVaultParse.NestedItemTypeKey, string(*props.Value.KeyName), string(*props.Value.Keyversion)) + key, err := keyVaultParse.NewNestedItemID(*props.Value.Keyvaulturi, keyVaultParse.NestedItemTypeKey, *props.Value.KeyName, *props.Value.Keyversion) if err == nil { d.Set("key_vault_key_id", key.ID()) } @@ -237,11 +237,7 @@ func databricksWorkspaceRootDbfsCustomerManagedKeyRead(d *pluginsdk.ResourceData } } - d.SetId(id.ID()) d.Set("workspace_id", id.ID()) - - // Always set this even if it's empty to keep the state file - // consistent with the configuration file... d.Set("key_vault_id", keyVaultId) return nil